From nobody Thu May  8 17:13:05 2025
Delivered-To: importer@patchew.org
Authentication-Results: mx.zohomail.com;
	dkim=pass;
	spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as
 permitted sender)
  smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org;
	dmarc=pass(p=none dis=none)  header.from=linaro.org
ARC-Seal: i=1; a=rsa-sha256; t=1673620382; cv=none;
	d=zohomail.com; s=zohoarc;
	b=M+QBerSGRBVi3OzEc03IL2dW3SIkQNdqoq/jugwTdzDZbfe8G1XijZFBZI6F0k2CbZGdUAtPHbXm2mxqVF5PCB5CJsEgWwf7edTHyvCzqkAD9XAexXjA50QiZHaNj4O+9osaZRpv09uxgjgv61EfxR3NH0IO+3sgMKcPjoKRq3I=
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com;
 s=zohoarc;
	t=1673620382;
 h=Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To;
	bh=a/R4jyoAfb0YQWGVRlN2GeE3T3fJjXtZYrTLqZefdTs=;
	b=CGCkeI8wyN1llY+Vqaqgl3JouClET22U8m1sMoMsvms3OuaNfE0qem6i5fyQQWdmPAfM9VuEvqGKJoONFKlBJbgD2HOBMtTO7DSO0GZtpz/WD9vQ3g19QGZodq9oq+dVzf2XxDNAU0aBb2S9JLQDv28+v+RhYwzNRAXElnnYlYc=
ARC-Authentication-Results: i=1; mx.zohomail.com;
	dkim=pass;
	spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as
 permitted sender)
  smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org;
	dmarc=pass header.from=<peter.maydell@linaro.org> (p=none dis=none)
Return-Path: <qemu-devel-bounces+importer=patchew.org@nongnu.org>
Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by
 mx.zohomail.com
	with SMTPS id 1673620382282485.1679115339348;
 Fri, 13 Jan 2023 06:33:02 -0800 (PST)
Received: from localhost ([::1] helo=lists1p.gnu.org)
	by lists.gnu.org with esmtp (Exim 4.90_1)
	(envelope-from <qemu-devel-bounces@nongnu.org>)
	id 1pGKmj-000101-Qk; Fri, 13 Jan 2023 09:11:57 -0500
Received: from eggs.gnu.org ([2001:470:142:3::10])
 by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <peter.maydell@linaro.org>)
 id 1pGKmS-0000r3-5s
 for qemu-devel@nongnu.org; Fri, 13 Jan 2023 09:11:40 -0500
Received: from mail-wm1-x32f.google.com ([2a00:1450:4864:20::32f])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.90_1) (envelope-from <peter.maydell@linaro.org>)
 id 1pGKmM-0003eW-7G
 for qemu-devel@nongnu.org; Fri, 13 Jan 2023 09:11:38 -0500
Received: by mail-wm1-x32f.google.com with SMTP id
 j16-20020a05600c1c1000b003d9ef8c274bso13089788wms.0
 for <qemu-devel@nongnu.org>; Fri, 13 Jan 2023 06:11:33 -0800 (PST)
Received: from orth.archaic.org.uk (orth.archaic.org.uk. [2001:8b0:1d0::2])
 by smtp.gmail.com with ESMTPSA id
 n36-20020a05600c502400b003da0b75de94sm5334464wmr.8.2023.01.13.06.11.31
 for <qemu-devel@nongnu.org>
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Fri, 13 Jan 2023 06:11:31 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:to:from:from:to:cc:subject:date:message-id
 :reply-to; bh=a/R4jyoAfb0YQWGVRlN2GeE3T3fJjXtZYrTLqZefdTs=;
 b=cGsomRBm9+hLJoKsUMZgLA5RFHlZzqRDvb/80NDuMP0Fvu8WlTOzk+lju+6C5NRJ8C
 nzQKeBftZHCPM4c8tA8zETfDHlq1LHxCOWXNuBOCNprOLtO680OOUJgeBH1otohsU2tR
 kdprmfOGCSAH0Pv/wMiGkKbUz+Ad41BvyyIYYzae6M1jrUeDvtsmmIvfVREYmGBdRflp
 mLMRe4E7Z+sNpSdpNABIa1xhbUSP+lPOHXzv7xFGwA9gU3wfXtl8e54PjBopOLwK1bYA
 sT1MI0hvpj96fhwIRP7RoIjN63P2qzbjvJZvsQnwqG+tbtIwq+H1Yd8xA5zazQwE+UOz
 txAA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20210112;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=a/R4jyoAfb0YQWGVRlN2GeE3T3fJjXtZYrTLqZefdTs=;
 b=jV0jlIV2AUpmCPjV29sPWLmkVQMOkBM1PfZfD178TIkbEV6Pp95NSoa/+KMeGEdh+f
 /+hz15gy/qwnsjbkMr+wmndq6Zp56OxNn03l8N0p7NA0ZhbZsjCmhV96p1GTd+DEyKjJ
 s8+5jSll5tNFw3h2v+VMpBiJvNCDJABCmll3YO+DJIXSoN7avaHNL1wSIR+AVJDRlxrp
 XCPQS3T2eihN9TJ5iVQJaDrl7WQFUsovU3O+QMCDzsKw63xd91kGKFQLq6q68uytYRNg
 JsNt7W9vVbDqA8iWOTEn4EOg0WcVQ75kWqcHVestZpVwSrW4BD/M+JZldORWm4EgOxX8
 fhHg==
X-Gm-Message-State: AFqh2krEN8HvNWYASIiLnL1FMynujwH9D/dOINOfyEs9CIh0GWr9umbM
 yuuSiYIyFBMS/UnoV9nWv+6R+wDGtGqwK9Kb
X-Google-Smtp-Source: 
 AMrXdXtGpH3gY84UUUWnt74BN7pxcnDD0cm437gCyLLESHy4trFNUIhD48fOuuQXYJBlUSTLeLK/zA==
X-Received: by 2002:a05:600c:4f8a:b0:3d9:f559:1f7e with SMTP id
 n10-20020a05600c4f8a00b003d9f5591f7emr13596464wmq.20.1673619092559;
 Fri, 13 Jan 2023 06:11:32 -0800 (PST)
From: Peter Maydell <peter.maydell@linaro.org>
To: qemu-devel@nongnu.org
Subject: [PULL 05/38] {hw/i2c,docs/system/arm}: Allwinner TWI/I2C Emulation
Date: Fri, 13 Jan 2023 14:10:53 +0000
Message-Id: <20230113141126.535646-6-peter.maydell@linaro.org>
X-Mailer: git-send-email 2.34.1
In-Reply-To: <20230113141126.535646-1-peter.maydell@linaro.org>
References: <20230113141126.535646-1-peter.maydell@linaro.org>
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17
 as permitted sender) client-ip=209.51.188.17;
 envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org;
 helo=lists.gnu.org;
Received-SPF: pass client-ip=2a00:1450:4864:20::32f;
 envelope-from=peter.maydell@linaro.org; helo=mail-wm1-x32f.google.com
X-Spam_score_int: -20
X-Spam_score: -2.1
X-Spam_bar: --
X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,
 DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,
 RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001,
 WEIRD_QUOTING=0.001 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-BeenThere: qemu-devel@nongnu.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: <qemu-devel.nongnu.org>
List-Unsubscribe: <https://lists.nongnu.org/mailman/options/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>
List-Archive: <https://lists.nongnu.org/archive/html/qemu-devel>
List-Post: <mailto:qemu-devel@nongnu.org>
List-Help: <mailto:qemu-devel-request@nongnu.org?subject=help>
List-Subscribe: <https://lists.nongnu.org/mailman/listinfo/qemu-devel>,
 <mailto:qemu-devel-request@nongnu.org?subject=subscribe>
Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org
Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org
X-ZohoMail-DKIM: pass (identity @linaro.org)
X-ZM-MESSAGEID: 1673620383030100001
Content-Type: text/plain; charset="utf-8"

From: Strahinja Jankovic <strahinjapjankovic@gmail.com>

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>
Message-id: 20221226220303.14420-4-strahinja.p.jankovic@gmail.com
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
---
 docs/system/arm/cubieboard.rst |   1 +
 docs/system/arm/orangepi.rst   |   1 +
 include/hw/arm/allwinner-a10.h |   2 +
 include/hw/arm/allwinner-h3.h  |   3 +
 include/hw/i2c/allwinner-i2c.h |  55 ++++
 hw/arm/allwinner-a10.c         |   8 +
 hw/arm/allwinner-h3.c          |  11 +-
 hw/i2c/allwinner-i2c.c         | 459 +++++++++++++++++++++++++++++++++
 hw/arm/Kconfig                 |   2 +
 hw/i2c/Kconfig                 |   4 +
 hw/i2c/meson.build             |   1 +
 hw/i2c/trace-events            |   5 +
 12 files changed, 551 insertions(+), 1 deletion(-)
 create mode 100644 include/hw/i2c/allwinner-i2c.h
 create mode 100644 hw/i2c/allwinner-i2c.c

diff --git a/docs/system/arm/cubieboard.rst b/docs/system/arm/cubieboard.rst
index 344ff8cef99..8d485f5435a 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 83c7445197b..e5973600a15 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)
=20
 Limitations
 """""""""""
diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h
index ad959d6395f..e569e661095 100644
--- a/include/hw/arm/allwinner-a10.h
+++ b/include/hw/arm/allwinner-a10.h
@@ -15,6 +15,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"
=20
 #include "target/arm/cpu.h"
 #include "qom/object.h"
@@ -40,6 +41,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 63025fb27c8..1d7ce205890 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"
=20
@@ -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 00000000000..4f378b86ba1
--- /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 WI=
THOUT
+ *  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 */
diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
index a5f7a36ac9b..17e439777e4 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
=20
 static void aw_a10_init(Object *obj)
 {
@@ -56,6 +57,8 @@ static void aw_a10_init(Object *obj)
=20
     object_initialize_child(obj, "sata", &s->sata, TYPE_ALLWINNER_AHCI);
=20
+    object_initialize_child(obj, "i2c0", &s->i2c0, TYPE_AW_I2C);
+
     if (machine_usb(current_machine)) {
         int i;
=20
@@ -176,6 +179,11 @@ static void aw_a10_realize(DeviceState *dev, Error **e=
rrp)
     /* RTC */
     sysbus_realize(SYS_BUS_DEVICE(&s->rtc), &error_fatal);
     sysbus_mmio_map_overlap(SYS_BUS_DEVICE(&s->rtc), 0, AW_A10_RTC_BASE, 1=
0);
+
+    /* 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));
 }
=20
 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 308ed155525..bfce3c8d92a 100644
--- a/hw/arm/allwinner-h3.c
+++ b/hw/arm/allwinner-h3.c
@@ -53,6 +53,7 @@ const hwaddr allwinner_h3_memmap[] =3D {
     [AW_H3_DEV_UART1]      =3D 0x01c28400,
     [AW_H3_DEV_UART2]      =3D 0x01c28800,
     [AW_H3_DEV_UART3]      =3D 0x01c28c00,
+    [AW_H3_DEV_TWI0]       =3D 0x01c2ac00,
     [AW_H3_DEV_EMAC]       =3D 0x01c30000,
     [AW_H3_DEV_DRAMCOM]    =3D 0x01c62000,
     [AW_H3_DEV_DRAMCTL]    =3D 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     =3D  1,
     AW_H3_GIC_SPI_UART2     =3D  2,
     AW_H3_GIC_SPI_UART3     =3D  3,
+    AW_H3_GIC_SPI_TWI0      =3D  6,
     AW_H3_GIC_SPI_TIMER0    =3D 18,
     AW_H3_GIC_SPI_TIMER1    =3D 19,
     AW_H3_GIC_SPI_MMC0      =3D 60,
@@ -225,6 +226,8 @@ static void allwinner_h3_init(Object *obj)
                               "ram-size");
=20
     object_initialize_child(obj, "rtc", &s->rtc, TYPE_AW_RTC_SUN6I);
+
+    object_initialize_child(obj, "twi0", &s->i2c0, TYPE_AW_I2C);
 }
=20
 static void allwinner_h3_realize(DeviceState *dev, Error **errp)
@@ -423,6 +426,12 @@ static void allwinner_h3_realize(DeviceState *dev, Err=
or **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]);
=20
+    /* 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_TWI=
0));
+
     /* Unimplemented devices */
     for (i =3D 0; i < ARRAY_SIZE(unimplemented); i++) {
         create_unimplemented_device(unimplemented[i].device_name,
diff --git a/hw/i2c/allwinner-i2c.c b/hw/i2c/allwinner-i2c.c
new file mode 100644
index 00000000000..a4359658362
--- /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 WI=
THOUT
+ *  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 =3D 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 =3D 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 =3D AW_I2C(obj);
+
+    if (STAT_TO_STA(s->stat) !=3D STAT_IDLE) {
+        i2c_end_transfer(s->bus);
+    }
+
+    s->addr  =3D TWI_ADDR_RESET;
+    s->xaddr =3D TWI_XADDR_RESET;
+    s->data  =3D TWI_DATA_RESET;
+    s->cntr  =3D TWI_CNTR_RESET;
+    s->stat  =3D TWI_STAT_RESET;
+    s->ccr   =3D TWI_CCR_RESET;
+    s->srst  =3D TWI_SRST_RESET;
+    s->efr   =3D TWI_EFR_RESET;
+    s->lcr   =3D 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) !=3D STAT_IDLE) {
+            s->cntr |=3D 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 =3D AW_I2C(opaque);
+
+    switch (offset) {
+    case TWI_ADDR_REG:
+        value =3D s->addr;
+        break;
+    case TWI_XADDR_REG:
+        value =3D s->xaddr;
+        break;
+    case TWI_DATA_REG:
+        if ((STAT_TO_STA(s->stat) =3D=3D STAT_M_ADDR_RD_ACK) ||
+            (STAT_TO_STA(s->stat) =3D=3D STAT_M_DATA_RX_ACK) ||
+            (STAT_TO_STA(s->stat) =3D=3D STAT_M_DATA_RX_NACK)) {
+            /* Get the next byte */
+            s->data =3D i2c_recv(s->bus);
+
+            if (s->cntr & TWI_CNTR_A_ACK) {
+                s->stat =3D STAT_FROM_STA(STAT_M_DATA_RX_ACK);
+            } else {
+                s->stat =3D STAT_FROM_STA(STAT_M_DATA_RX_NACK);
+            }
+            allwinner_i2c_raise_interrupt(s);
+        }
+        value =3D s->data;
+        break;
+    case TWI_CNTR_REG:
+        value =3D s->cntr;
+        break;
+    case TWI_STAT_REG:
+        value =3D s->stat;
+        /*
+         * If polling when reading then change state to indicate data
+         * is available
+         */
+        if (STAT_TO_STA(s->stat) =3D=3D STAT_M_ADDR_RD_ACK) {
+            if (s->cntr & TWI_CNTR_A_ACK) {
+                s->stat =3D STAT_FROM_STA(STAT_M_DATA_RX_ACK);
+            } else {
+                s->stat =3D STAT_FROM_STA(STAT_M_DATA_RX_NACK);
+            }
+            allwinner_i2c_raise_interrupt(s);
+        }
+        break;
+    case TWI_CCR_REG:
+        value =3D s->ccr;
+        break;
+    case TWI_SRST_REG:
+        value =3D s->srst;
+        break;
+    case TWI_EFR_REG:
+        value =3D s->efr;
+        break;
+    case TWI_LCR_REG:
+        value =3D 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 =3D 0;
+        break;
+    }
+
+    trace_allwinner_i2c_read(allwinner_i2c_get_regname(offset), offset, va=
lue);
+
+    return (uint64_t)value;
+}
+
+static void allwinner_i2c_write(void *opaque, hwaddr offset,
+                                uint64_t value, unsigned size)
+{
+    AWI2CState *s =3D AW_I2C(opaque);
+
+    value &=3D 0xff;
+
+    trace_allwinner_i2c_write(allwinner_i2c_get_regname(offset), offset, v=
alue);
+
+    switch (offset) {
+    case TWI_ADDR_REG:
+        s->addr =3D (uint8_t)value;
+        break;
+    case TWI_XADDR_REG:
+        s->xaddr =3D (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 =3D 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 =3D STAT_FROM_STA(STAT_M_ADDR_WR_NACK);
+            } else {
+                /* Determine if read of write */
+                if (extract32(s->data, 0, 1)) {
+                    s->stat =3D STAT_FROM_STA(STAT_M_ADDR_RD_ACK);
+                } else {
+                    s->stat =3D 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 =3D STAT_FROM_STA(STAT_M_DATA_TX_NACK);
+                i2c_end_transfer(s->bus);
+            } else {
+                s->stat =3D 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 =3D 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) =3D=3D STAT_IDLE) {
+                    /* Send start condition */
+                    s->stat =3D STAT_FROM_STA(STAT_M_STA_TX);
+                } else {
+                    /* Send repeated start condition */
+                    s->stat =3D STAT_FROM_STA(STAT_M_RSTA_TX);
+                }
+                /* Clear start condition */
+                s->cntr &=3D ~TWI_CNTR_M_STA;
+            }
+            if (s->cntr & TWI_CNTR_M_STP) {
+                /* Update status */
+                i2c_end_transfer(s->bus);
+                s->stat =3D STAT_FROM_STA(STAT_IDLE);
+                s->cntr &=3D ~TWI_CNTR_M_STP;
+            }
+            if ((s->cntr & TWI_CNTR_INT_FLAG) =3D=3D 0) {
+                /* Interrupt flag cleared */
+                qemu_irq_lower(s->irq);
+            }
+            if ((s->cntr & TWI_CNTR_A_ACK) =3D=3D 0) {
+                if (STAT_TO_STA(s->stat) =3D=3D STAT_M_DATA_RX_ACK) {
+                    s->stat =3D STAT_FROM_STA(STAT_M_DATA_RX_NACK);
+                }
+            } else {
+                if (STAT_TO_STA(s->stat) =3D=3D STAT_M_DATA_RX_NACK) {
+                    s->stat =3D STAT_FROM_STA(STAT_M_DATA_RX_ACK);
+                }
+            }
+            allwinner_i2c_raise_interrupt(s);
+
+        }
+        break;
+    case TWI_CCR_REG:
+        s->ccr =3D value & TWI_CCR_MASK;
+        break;
+    case TWI_SRST_REG:
+        if (((value & TWI_SRST_MASK) =3D=3D 0) && (s->srst & TWI_SRST_MASK=
)) {
+            /* Perform reset */
+            allwinner_i2c_reset_hold(OBJECT(s));
+        }
+        s->srst =3D value & TWI_SRST_MASK;
+        break;
+    case TWI_EFR_REG:
+        s->efr =3D value & TWI_EFR_MASK;
+        break;
+    case TWI_LCR_REG:
+        s->lcr =3D 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 =3D {
+    .read =3D allwinner_i2c_read,
+    .write =3D allwinner_i2c_write,
+    .valid.min_access_size =3D 1,
+    .valid.max_access_size =3D 4,
+    .endianness =3D DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription allwinner_i2c_vmstate =3D {
+    .name =3D TYPE_AW_I2C,
+    .version_id =3D 1,
+    .minimum_version_id =3D 1,
+    .fields =3D (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 =3D 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 =3D i2c_init_bus(dev, "i2c");
+}
+
+static void allwinner_i2c_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc =3D DEVICE_CLASS(klass);
+    ResettableClass *rc =3D RESETTABLE_CLASS(klass);
+
+    rc->phases.hold =3D allwinner_i2c_reset_hold;
+    dc->vmsd =3D &allwinner_i2c_vmstate;
+    dc->realize =3D allwinner_i2c_realize;
+    dc->desc =3D "Allwinner I2C Controller";
+}
+
+static const TypeInfo allwinner_i2c_type_info =3D {
+    .name =3D TYPE_AW_I2C,
+    .parent =3D TYPE_SYS_BUS_DEVICE,
+    .instance_size =3D sizeof(AWI2CState),
+    .class_init =3D 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/arm/Kconfig b/hw/arm/Kconfig
index 9ce756fca77..3e9b2a23fd5 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -326,6 +326,7 @@ config ALLWINNER_A10
     select ALLWINNER_A10_CCM
     select ALLWINNER_A10_DRAMC
     select ALLWINNER_EMAC
+    select ALLWINNER_I2C
     select SERIAL
     select UNIMP
=20
@@ -333,6 +334,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/i2c/Kconfig b/hw/i2c/Kconfig
index 9bb8870517f..f8ec461be3d 100644
--- a/hw/i2c/Kconfig
+++ b/hw/i2c/Kconfig
@@ -34,6 +34,10 @@ config MPC_I2C
     bool
     select I2C
=20
+config ALLWINNER_I2C
+    bool
+    select I2C
+
 config PCA954X
     bool
     select I2C
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
index 6e7340aaacc..e4c8e14a527 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('bitb=
ang_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 af181d43ee6..52dbd53a234 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) ""
=20
+# 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
=20
 aspeed_i2c_bus_cmd(uint32_t cmd, const char *cmd_flags, uint32_t count, ui=
nt32_t intr_status) "handling cmd=3D0x%x %s count=3D%d intr=3D0x%x"
--=20
2.34.1