From nobody Sat Sep 26 20:50:55 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; 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=fail(p=none dis=none) header.from=gmail.com Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1789504124483135.20079959537918; Tue, 15 Sep 2026 13:28:44 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x6ZkQ-0006hP-AQ; Tue, 15 Sep 2026 16:27:22 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x6ZkO-0006gn-Dq for qemu-devel@nongnu.org; Tue, 15 Sep 2026 16:27:20 -0400 Received: from mail-lr2-x0f.google.com ([2a00:1450:4864:38::f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1x6ZkK-0004nm-P0 for qemu-devel@nongnu.org; Tue, 15 Sep 2026 16:27:19 -0400 Received: by mail-lr2-x0f.google.com with SMTP id 38308e7fff4ca-3a30521ef15so2096161fa.0 for ; Tue, 15 Sep 2026 13:27:16 -0700 (PDT) Received: from [192.168.31.141] (213-65-111-204-no600.tbcn.telia.com. [213.65.111.204]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-3a5daf0dbacsm1621391fa.4.2026.09.15.13.27.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Sep 2026 13:27:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789504035; x=1790108835; darn=nongnu.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:sender:from:to:cc :subject:date:message-id:reply-to:content-type; bh=WPved3Pkyng6AEdxchj5cI8SWt2QbXxlobXdSxRyYPM=; b=GnbgAGUoY2RKa+59kWi61KUwtCOyq7vTl6Xc7mGuzLppPjIj1yLVOHCWptc9+eYIAx 6s02F8+Nb/fvoZd88cnrs4ZDDt5wlg8vxMSp5C+Fp3NhNy0PeW/XOY5Tf9D/5FweQx+N H3Dw2MK9y73DYviziYktXxnMM7vYsXWZTUEdp1tSR8uXeOIKr4jLBCZoq05SZfBevszR A3EYHqnag0jTFMy8FOgaBJHkRoHbz2FbJc3rleYYKbvw/C8gc4VLlKRJ1Uwx1zJVRU1S klmBBmNtSoJTBiySE726+ABHo0UVgS8GhWw/yatTHs3PdcVW+WnSmqNRWyfsiWFJxy+a RAGg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789504035; x=1790108835; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:sender:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=WPved3Pkyng6AEdxchj5cI8SWt2QbXxlobXdSxRyYPM=; b=uNq5v1oU8w9K3bF4lTYNhYcvdfkDN7GC89hRAJTKg49YE6CAyOnfC9q6nEGjk5dqNH zCH7/n5ZlCoXopV/8HjLuXTs9rIjklQZ1/N+xDKMSSxYUOzSYTwz+gGHC18P1gTcsXH2 VWqo/UW3q0alBwrNaIuUL+v4fcialngPBkJur6zrRExTcBInkjyiuIPzhmLLHN4eiWzX N0twb0ajTNML9a6nvowiBtYd1jJya/jCwGndPBvqrw95ERiJ7wTvl7v3hHgehX8qcEzZ 9nuPMMHjcT4s3n1qJYrZU25V+humll5Oir/YFhWmJd2WMibHDSyusT62MvO4spNf7gze Efsg== X-Gm-Message-State: AFuF++kw+JBsejEqGgHq0MFQjr9D1PHB2bJrcJMUVVQnwRYjsVz2aD5Z nv+VYfyXukMLo/E0f3kpCI+a+yi1voJHxTUN6YTgrKi5ToWsYRecqqvn X-Gm-Gg: AYBFou2yrCHwelb3T21n2ZNrCGLwrGoj9iTLRWs4xKCm9TffeEXF93mPuVaMeb6bEG5 VASpvQ5qg4uooFLZQMDkobZlyAveWgB6tnoIUUx3Se5U3jZAWwZpWkPKGvNx/ezfTa2ld8YIisc VVHTWvQzeZVMEVd4oBin6p/sP/fsVWjMA6bT6eP/ldz829lDfgYjj0AlREka/LpZXNl4272/ec9 F7bhN5w4/0a1+T92FS3MYUCmy5Y0EUG6dCmRsQ5PB3k/8DQoGg0lWqEsyYPOpnlG4F1PC+NtWaV MMYrEBdCS9ExEuAHVCtKYCSM2GPp8rRZIq/7sxciByz6TAswInooa2y3CRj04aVZpyrzjat5Ort v9qLn0AWzH06SlUeKYz1yEj0hg97KgoENWgL4P1wlt/vlMvkxTZu87srvgjjGZFEetNA2h2UCSS LGoG1XKJjMsqDhPeDt9R4BsyG+Yasd7OcwEzxQmlKJD7hKEsTQqLpwopewjOcU7XkBZ1y+VcFCi HOB5Xhwu8glTx36wsuNjDHe7M92R/nVx7aAzTu1jZmMnxaJRK6FeOVDsqbql1rmU5IS6g== X-Received: by 2002:a05:651c:2107:b0:3a5:a106:ee0 with SMTP id 38308e7fff4ca-3a5dca17909mr173011fa.24.1789504034750; Tue, 15 Sep 2026 13:27:14 -0700 (PDT) From: Strahinja Jankovic Date: Tue, 15 Sep 2026 22:27:04 +0200 Subject: [PATCH v4 1/3] hw/gpio: Allwinner A10 GPIO emulation MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260915-allwinner-gpio-v4-1-b1ee1d09c9fd@gmail.com> References: <20260915-allwinner-gpio-v4-0-b1ee1d09c9fd@gmail.com> In-Reply-To: <20260915-allwinner-gpio-v4-0-b1ee1d09c9fd@gmail.com> To: qemu-devel@nongnu.org Cc: Paolo Bonzini , Peter Maydell , qemu-arm@nongnu.org, Pierrick Bouvier , Fabiano Rosas , Laurent Vivier , Jack wang <163wangjack@gmail.com>, Strahinja Jankovic X-Mailer: b4 0.14.3 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=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:38::f; envelope-from=strahinja.p.jankovic@gmail.com; helo=mail-lr2-x0f.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, FREEMAIL_FROM=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-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 development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1789504125647158500 This patch implements Allwinner A10 GPIO peripheral emulation. The emulation focuses on input/output and interrupt functionality, whereas the drive and pull up/down functionality is not emulated. Signed-off-by: Strahinja Jankovic --- MAINTAINERS | 2 + hw/gpio/Kconfig | 3 + hw/gpio/allwinner-gpio.c | 441 ++++++++++++++++++++++++++++++= ++++ hw/gpio/meson.build | 1 + hw/gpio/trace-events | 6 + include/hw/gpio/allwinner-gpio-regs.h | 227 +++++++++++++++++ include/hw/gpio/allwinner-gpio.h | 41 ++++ 7 files changed, 721 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 5bf895732c..b453edc820 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -746,6 +746,8 @@ F: include/hw/*/allwinner* F: hw/arm/cubieboard.c F: docs/system/arm/cubieboard.rst F: hw/misc/axp2xx.c +F: hw/gpio/allwinner-gpio.c +F: include/hw/gpio/allwinner-gpio* F: tests/functional/arm/test_cubieboard.py =20 Allwinner-h3 diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig index fcc7c70bd5..ea0fb72b40 100644 --- a/hw/gpio/Kconfig +++ b/hw/gpio/Kconfig @@ -33,3 +33,6 @@ config ZAURUS_SCOOP =20 config CADENCE_GPIO bool + +config ALLWINNER_GPIO + bool diff --git a/hw/gpio/allwinner-gpio.c b/hw/gpio/allwinner-gpio.c new file mode 100644 index 0000000000..e3bda7cb76 --- /dev/null +++ b/hw/gpio/allwinner-gpio.c @@ -0,0 +1,441 @@ +/* + * Allwinner GPIO Emulation + * + * Copyright (C) 2026 Strahinja Jankovic + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/gpio/allwinner-gpio.h" +#include "hw/gpio/allwinner-gpio-regs.h" +#include "hw/core/irq.h" +#include "hw/core/qdev-properties.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "trace.h" + +#define REG_INDEX(offset) (offset / sizeof(uint32_t)) + +#define AW_GPIO_SET(port) \ + static inline void allwinner_gpio_set_##port(void *opaque, \ + int line, \ + int level) { \ + allwinner_gpio_set(opaque, port, line, level); \ + } + +static void allwinner_gpio_set(void *opaque, int port, int line, int level= ); + +AW_GPIO_SET(GPIO_PA); +AW_GPIO_SET(GPIO_PB); +AW_GPIO_SET(GPIO_PC); +AW_GPIO_SET(GPIO_PD); +AW_GPIO_SET(GPIO_PE); +AW_GPIO_SET(GPIO_PF); +AW_GPIO_SET(GPIO_PG); +AW_GPIO_SET(GPIO_PH); +AW_GPIO_SET(GPIO_PI); + +static void (*AW_GPIO_IN_HANDLER[AW_GPIO_PORTS_NUM]) (void *opaque, + int line, + int level) =3D { + allwinner_gpio_set_GPIO_PA, + allwinner_gpio_set_GPIO_PB, + allwinner_gpio_set_GPIO_PC, + allwinner_gpio_set_GPIO_PD, + allwinner_gpio_set_GPIO_PE, + allwinner_gpio_set_GPIO_PF, + allwinner_gpio_set_GPIO_PG, + allwinner_gpio_set_GPIO_PH, + allwinner_gpio_set_GPIO_PI, +}; + +static char *allwinner_gpio_get_regname(unsigned offset) +{ + switch (offset) { + case 0 ... GPIO_Pn_PUL1(GPIO_PI): + { + const char *regname =3D "?"; + switch (offset % PORT_STRIDE) { + case CFG0: + regname =3D "CFG0"; + break; + case CFG1: + regname =3D "CFG1"; + break; + case CFG2: + regname =3D "CFG2"; + break; + case CFG3: + regname =3D "CFG3"; + break; + case DAT: + regname =3D "DAT"; + break; + case DRV0: + regname =3D "DRV0"; + break; + case DRV1: + regname =3D "DRV1"; + break; + case PUL0: + regname =3D "PUL0"; + break; + case PUL1: + regname =3D "PUL1"; + break; + } + return g_strdup_printf("%s:%s", + portname(offset / PORT_STRIDE), + regname); + } + case GPIO_INT_CFG0: + return g_strdup("INT_CFG0"); + case GPIO_INT_CFG1: + return g_strdup("INT_CFG1"); + case GPIO_INT_CFG2: + return g_strdup("INT_CFG2"); + case GPIO_INT_CFG3: + return g_strdup("INT_CFG3"); + case GPIO_INT_CTL: + return g_strdup("INT_CTL"); + case GPIO_INT_STA: + return g_strdup("INT_STA"); + case GPIO_INT_DEB: + return g_strdup("INT_DEB"); + default: + return g_strdup("[?]"); + } +} + +static inline bool gpio_is_output(AWPortMap *port, uint32_t pin) +{ + uint32_t cfg_n =3D pin / CFG_PINS_PER_REG; + uint32_t pin_shift =3D (pin % CFG_PINS_PER_REG) * CFG_PIN_STRIDE; + return (extract32(port->cfg[cfg_n], pin_shift, CFG_PIN_STRIDE - 1) =3D= =3D + CFG_OUTPUT_MASK); +} + +static inline bool gpio_is_input(AWPortMap *port, uint32_t pin) +{ + uint32_t cfg_n =3D pin / CFG_PINS_PER_REG; + uint32_t pin_shift =3D (pin % CFG_PINS_PER_REG) * CFG_PIN_STRIDE; + return (extract32(port->cfg[cfg_n], pin_shift, CFG_PIN_STRIDE - 1) =3D= =3D + CFG_INPUT_MASK); +} + +static inline int int_ctl_cfg(AWGPIOState *s, int irq_line) +{ + unsigned int_cfg_n =3D s->regs[REG_INDEX(GPIO_INT_CFG0) + irq_line / 8= ]; + return extract32(int_cfg_n, + (irq_line % INT_CFG_IRQ_PER_REG) * INT_CFG_IRQ_STRIDE, + INT_CFG_IRQ_STRIDE); +} + +static inline void allwinner_gpio_update_int(AWGPIOState *s) +{ + qemu_set_irq( + s->irq, + !!(s->regs[REG_INDEX(GPIO_INT_CTL)] & s->regs[REG_INDEX(GPIO_INT_S= TA)]) + ); +} + +static void allwinner_gpio_set_int_line(AWGPIOState *s, int port, + int line, AWGPIOLevel level) +{ + AWPortsOverlay *o =3D (AWPortsOverlay *)s->regs; + int irq_line =3D irq_nr(port, line); + bool raise_irq =3D false; + unsigned input_val; + int int_cfg; + + /* Check if pin can actually trigger an interrupt */ + if (irq_line =3D=3D -1) { + return; + } + + /* if this signal isn't configured as an input signal, nothing to do */ + if (!gpio_is_input(&o->ports[port], line)) { + return; + } + + /* Get current input value */ + input_val =3D extract32(o->ports[port].dat, line, 1); + int_cfg =3D int_ctl_cfg(s, irq_line); + + switch (int_cfg) { + case AW_GPIO_IRQ_CFG_RISING_EDGE: + if (!input_val && level) { + raise_irq =3D true; + } + break; + case AW_GPIO_IRQ_CFG_FALLING_EDGE: + if (input_val && !level) { + raise_irq =3D true; + } + break; + case AW_GPIO_IRQ_CFG_HIGH_LEVEL: + if (level) { + raise_irq =3D true; + } + break; + case AW_GPIO_IRQ_CFG_LOW_LEVEL: + if (!level) { + raise_irq =3D true; + } + break; + case AW_GPIO_IRQ_CFG_BOTH_EDGE: + if (input_val !=3D level) { + raise_irq =3D true; + } + break; + default: + /* Unexpected */ + break; + } + + if (raise_irq) { + s->regs[REG_INDEX(GPIO_INT_STA)] |=3D 1u << irq_line; + } +} + +static void port_set_all_int_lines(AWGPIOState *s, int port) +{ + AWPortsOverlay *o =3D (AWPortsOverlay *)s->regs; + + for (int i =3D 0; i < AW_PINS_PER_PORT[port]; i++) { + AWGPIOLevel aw_level =3D extract32(o->ports[port].dat, i, 1); + allwinner_gpio_set_int_line(s, port, i, aw_level); + } + + allwinner_gpio_update_int(s); +} + +static void allwinner_set_all_int_lines(AWGPIOState *s) +{ + for (int i =3D 0; i < AW_GPIO_PORTS_NUM; i++) { + port_set_all_int_lines(s, i); + } +} + +static void allwinner_gpio_set(void *opaque, int port, int line, int level) +{ + AWGPIOState *s =3D AW_GPIO(opaque); + AWPortsOverlay *o =3D (AWPortsOverlay *)s->regs; + AWGPIOLevel aw_level =3D level ? AW_GPIO_LEVEL_HIGH : AW_GPIO_LEVEL_LO= W; + + if (gpio_is_input(&o->ports[port], line)) { + + trace_allwinner_gpio_set(portname(port), line, aw_level); + + allwinner_gpio_set_int_line(s, port, line, aw_level); + o->ports[port].dat =3D deposit32(o->ports[port].dat, line, 1, aw_l= evel); + } + allwinner_gpio_update_int(s); +} + + +static inline void port_update_output_lines(AWGPIOState *s, uint32_t port) +{ + AWPortsOverlay *o =3D (AWPortsOverlay *)s->regs; + + for (int pin =3D 0; pin < AW_PINS_PER_PORT[port]; pin++) { + if (gpio_is_output(&o->ports[port], pin)) { + qemu_set_irq(s->output[port][pin], + !!(o->ports[port].dat & BIT_MASK(pin))); + trace_allwinner_gpio_out_pin( + portname(port), pin, + !!(o->ports[port].dat & BIT_MASK(pin))); + } + else { + qemu_irq_lower(s->output[port][pin]); + } + } +} + +static uint64_t allwinner_gpio_read(void *opaque, hwaddr offset, unsigned = size) +{ + AWGPIOState *s =3D AW_GPIO(opaque); + uint32_t reg_value =3D 0; + g_autofree char *regname =3D allwinner_gpio_get_regname(offset); + + switch (offset) { + case 0 ... GPIO_Pn_PUL1(GPIO_PI): + case GPIO_INT_CFG0: + case GPIO_INT_CFG1: + case GPIO_INT_CFG2: + case GPIO_INT_CFG3: + case GPIO_INT_CTL: + case GPIO_INT_STA: + case GPIO_INT_DEB: + reg_value =3D s->regs[REG_INDEX(offset)]; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" + HWADDR_PRIx "\n", TYPE_AW_GPIO, __func__, offset); + break; + } + + trace_allwinner_gpio_read(regname, reg_value); + + return reg_value; +} + +static void allwinner_port_write(AWGPIOState *s, hwaddr offset, uint64_t v= alue) +{ + AWPortsOverlay *o =3D (AWPortsOverlay *)s->regs; + uint32_t port =3D offset / PORT_STRIDE; + uint32_t reg =3D offset % PORT_STRIDE; + + switch (reg) { + case CFG0: + o->ports[port].cfg[0] =3D + value & GPIO_CFG0_PINS_MASK(AW_PINS_PER_PORT[port]); + break; + case CFG1: + o->ports[port].cfg[1] =3D + value & GPIO_CFG1_PINS_MASK(AW_PINS_PER_PORT[port]); + break; + case CFG2: + o->ports[port].cfg[2] =3D + value & GPIO_CFG2_PINS_MASK(AW_PINS_PER_PORT[port]); + break; + case CFG3: + o->ports[port].cfg[3] =3D + value & GPIO_CFG3_PINS_MASK(AW_PINS_PER_PORT[port]); + break; + case DAT: + o->ports[port].dat =3D + value & GPIO_DAT_PINS_MASK(AW_PINS_PER_PORT[port]); + break; + case DRV0: + o->ports[port].drv[0] =3D + value & GPIO_DRV0_PINS_MASK(AW_PINS_PER_PORT[port]); + break; + case DRV1: + o->ports[port].drv[1] =3D + value & GPIO_DRV1_PINS_MASK(AW_PINS_PER_PORT[port]); + break; + case PUL0: + o->ports[port].pul[0] =3D + value & GPIO_PUL0_PINS_MASK(AW_PINS_PER_PORT[port]); + break; + case PUL1: + o->ports[port].pul[1] =3D + value & GPIO_PUL1_PINS_MASK(AW_PINS_PER_PORT[port]); + break; + } + port_update_output_lines(s, port); + port_set_all_int_lines(s, port); +} + +static void allwinner_gpio_write(void *opaque, hwaddr offset, uint64_t val= ue, + unsigned size) +{ + AWGPIOState *s =3D AW_GPIO(opaque); + g_autofree char *regname =3D allwinner_gpio_get_regname(offset); + + trace_allwinner_gpio_write(regname, value); + + switch (offset) { + case 0 ... GPIO_Pn_PUL1(GPIO_PI): + allwinner_port_write(s, offset, value); + break; + case GPIO_INT_CFG0: + case GPIO_INT_CFG1: + case GPIO_INT_CFG2: + case GPIO_INT_CFG3: + case GPIO_INT_CTL: + case GPIO_INT_DEB: + s->regs[REG_INDEX(offset)] =3D value; + /* update interrupts */ + allwinner_set_all_int_lines(s); + break; + case GPIO_INT_STA: + /* W1C */ + s->regs[REG_INDEX(offset)] &=3D ~value; + allwinner_set_all_int_lines(s); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" + HWADDR_PRIx "\n", TYPE_AW_GPIO, __func__, offset); + break; + } +} + +static const MemoryRegionOps allwinner_gpio_ops =3D { + .read =3D allwinner_gpio_read, + .write =3D allwinner_gpio_write, + .valid.min_access_size =3D 4, + .valid.max_access_size =3D 4, + .endianness =3D DEVICE_LITTLE_ENDIAN, +}; + +static const VMStateDescription vmstate_allwinner_gpio =3D { + .name =3D TYPE_AW_GPIO, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, AWGPIOState, AW_GPIO_REGS_NUM), + VMSTATE_END_OF_LIST() + } +}; + +static void allwinner_gpio_reset(DeviceState *dev) +{ + AWGPIOState *s =3D AW_GPIO(dev); + AWPortsOverlay *o =3D (AWPortsOverlay *)s->regs; + + memset(s->regs, 0, AW_GPIO_IOSIZE); + + for (int port =3D 0; port < AW_GPIO_PORTS_NUM; port++) { + o->ports[port].drv[0] =3D aw_gpio_port_reset[port].drv[0]; + o->ports[port].drv[1] =3D aw_gpio_port_reset[port].drv[1]; + o->ports[port].pul[0] =3D aw_gpio_port_reset[port].pul[0]; + o->ports[port].pul[1] =3D aw_gpio_port_reset[port].pul[1]; + } +} + +static void allwinner_gpio_realize(DeviceState *dev, Error **errp) +{ + AWGPIOState *s =3D AW_GPIO(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_gpio_ops, s, + TYPE_AW_GPIO, AW_GPIO_IOSIZE); + + for (int port =3D 0; port < AW_GPIO_PORTS_NUM; port++) { + g_autofree char *in_name =3D portname_in(port); + g_autofree char *out_name =3D portname_out(port); + qdev_init_gpio_in_named(dev, AW_GPIO_IN_HANDLER[port], + in_name, AW_PINS_PER_PORT[port]); + qdev_init_gpio_out_named(dev, s->output[port], + out_name, AW_PINS_PER_PORT[port]); + } + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); +} + +static void allwinner_gpio_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->realize =3D allwinner_gpio_realize; + device_class_set_legacy_reset(dc, allwinner_gpio_reset); + dc->vmsd =3D &vmstate_allwinner_gpio; + dc->desc =3D "Allwinner GPIO controller"; +} + +static const TypeInfo allwinner_gpio_info =3D { + .name =3D TYPE_AW_GPIO, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(AWGPIOState), + .class_init =3D allwinner_gpio_class_init, +}; + +static void allwinner_gpio_register_types(void) +{ + type_register_static(&allwinner_gpio_info); +} + +type_init(allwinner_gpio_register_types) diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build index 0555f44b6a..8ede5e88ae 100644 --- a/hw/gpio/meson.build +++ b/hw/gpio/meson.build @@ -20,3 +20,4 @@ system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('= aspeed_sgpio.c')) system_ss.add(when: 'CONFIG_SIFIVE_GPIO', if_true: files('sifive_gpio.c')) system_ss.add(when: 'CONFIG_PCF8574', if_true: files('pcf8574.c')) system_ss.add(when: 'CONFIG_CADENCE_GPIO', if_true: files('cadence_gpio.c'= )) +system_ss.add(when: 'CONFIG_ALLWINNER_GPIO', if_true: files('allwinner-gpi= o.c')) diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events index 80ca783a03..5ef3e0b8fd 100644 --- a/hw/gpio/trace-events +++ b/hw/gpio/trace-events @@ -51,3 +51,9 @@ stm32l4x5_gpio_pins(char *gpio, uint16_t disconnected, ui= nt16_t high) "GPIO%s di cdns_gpio_read(const char *path, uint64_t offset, uint32_t value) "%s:reg[= 0x%04" PRIx64 "] -> 0x%" PRIx32 cdns_gpio_write(const char *path, uint64_t offset, uint64_t value) "%s:reg= [0x%04" PRIx64 "] <- 0x%04" PRIx64 cdns_gpio_set(const char *path, int line, int level) "%s:[%d] <- %d" + +# allwinner_gpio.c +allwinner_gpio_set(const char *port, int line, int level) "[%s:%d] <- %d" +allwinner_gpio_read(const char *reg, uint32_t value) "[%s] -> 0x%" PRIx32 +allwinner_gpio_write(const char *reg, uint32_t value) "[%s] <- 0x%" PRIx32 +allwinner_gpio_out_pin(const char *reg, uint32_t pin, uint32_t value) "[%s= .%d] <- 0x%" PRIx32 diff --git a/include/hw/gpio/allwinner-gpio-regs.h b/include/hw/gpio/allwin= ner-gpio-regs.h new file mode 100644 index 0000000000..a435b87ec4 --- /dev/null +++ b/include/hw/gpio/allwinner-gpio-regs.h @@ -0,0 +1,227 @@ +/* + * Allwinner GPIO registers definition + * + * Copyright (C) 2026 Strahinja Jankovic. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ALLWINNER_GPIO_REGS_H +#define ALLWINNER_GPIO_REGS_H + +/* GPIO ports index (n) */ +#define GPIO_PA 0 +#define GPIO_PB 1 +#define GPIO_PC 2 +#define GPIO_PD 3 +#define GPIO_PE 4 +#define GPIO_PF 5 +#define GPIO_PG 6 +#define GPIO_PH 7 +#define GPIO_PI 8 + +#ifndef AW_GPIO_PORTS_NUM +#define AW_GPIO_PORTS_NUM 9 +#endif + +#ifndef AW_GPIO_PIN_COUNT +#define AW_GPIO_PIN_COUNT 32 +#endif + +typedef struct AWPortMap { + uint32_t cfg[4]; + uint32_t dat; + uint32_t drv[2]; + uint32_t pul[2]; +} AWPortMap; + +typedef struct AWPortsOverlay { + AWPortMap ports[AW_GPIO_PORTS_NUM]; +} AWPortsOverlay; + +static const uint32_t AW_PINS_PER_PORT[AW_GPIO_PORTS_NUM] =3D { + 18, + 24, + 25, + 28, + 12, + 6, + 12, + 28, + 22 +}; + +typedef struct { + uint32_t drv[2]; + uint32_t pul[2]; +} AWPortResetVals; + +static const AWPortResetVals aw_gpio_port_reset[] =3D { + [GPIO_PA] =3D { .drv =3D { 0x55555555, 0x00000005 } }, + [GPIO_PB] =3D { .drv =3D { 0x55555555, 0x00005555 } }, + [GPIO_PC] =3D { .drv =3D { 0x55555555, 0x00015555 }, + .pul =3D { 0x00005140, 0x00004016 } }, + [GPIO_PD] =3D { .drv =3D { 0x55555555, 0x00555555 } }, + [GPIO_PE] =3D { .drv =3D { 0x00555555, 0x0 } }, + [GPIO_PF] =3D { .drv =3D { 0x00000555, 0x0 } }, + [GPIO_PG] =3D { .drv =3D { 0x05555555, 0x0 } }, + [GPIO_PH] =3D { .drv =3D { 0x55555555, 0x00555555 } }, + [GPIO_PI] =3D { .drv =3D { 0x55555555, 0x00000555 } }, +}; + +#define DEFAULT_CFG_MASK 0x77777777 +#define DEFAULT_DRV_MASK 0xffffffff +#define DEFAULT_PUL_MASK 0xffffffff + +#define CFG_INPUT_MASK 0x0 +#define CFG_OUTPUT_MASK 0x1 +#define CFG_IO_MASK 0x1 +#define CFG_PIN_STRIDE 4 +#define CFG_PINS_PER_REG (AW_GPIO_PIN_COUNT / CFG_PIN_STRIDE) + +#define AW_INT_GPIO_COUNT 32 +#define INT_CFG_IRQ_STRIDE 4 +#define INT_CFG_IRQ_PER_REG (AW_INT_GPIO_COUNT / INT_CFG_IRQ_STRIDE) + +/* GPIO masks per number of pins */ +#define GPIO_CFG0_PINS_MASK(pins) \ + ((pins >=3D CFG_PINS_PER_REG) ? DEFAULT_CFG_MASK : \ + ((1 << (pins * CFG_PIN_STRIDE)) - 1) & DEFAULT_CFG_MASK) + +#define GPIO_CFG1_PINS_MASK(pins) \ + ((pins >=3D 16) ? DEFAULT_CFG_MASK : \ + ((pins > 8) ? ((1 << ((pins - 8) * 4)) - 1) & DEFAULT_CFG_MASK : 0)) + +#define GPIO_CFG2_PINS_MASK(pins) \ + ((pins >=3D 24) ? DEFAULT_CFG_MASK : \ + ((pins > 16) ? ((1 << ((pins - 16) * 4)) - 1) & DEFAULT_CFG_MASK : 0)) + +#define GPIO_CFG3_PINS_MASK(pins) \ + ((pins > 24) ? ((1 << ((pins - 24) * 4)) - 1) & DEFAULT_CFG_MASK : 0) + +#define GPIO_DAT_PINS_MASK(pins) ((1 << pins) - 1) + +#define GPIO_DRV0_PINS_MASK(pins) \ + ((pins > 16) ? DEFAULT_DRV_MASK : ((1 << (pins * 2)) - 1)) + +#define GPIO_DRV1_PINS_MASK(pins) \ + ((pins > 16) ? ((1 << ((pins - 16) * 2)) - 1) : 0) + +#define GPIO_PUL0_PINS_MASK(pins) \ + ((pins > 16) ? DEFAULT_PUL_MASK : ((1 << (pins * 2)) - 1)) + +#define GPIO_PUL1_PINS_MASK(pins) \ + ((pins > 16) ? ((1 << ((pins - 16) * 2)) - 1) : 0) + + +#define PORT_STRIDE 0x24 + +/* Allwinner GPIO memory map */ +#define CFG0 0x00 /* Configure register 0 */ +#define CFG1 0x04 /* Configure register 1 */ +#define CFG2 0x08 /* Configure register 2 */ +#define CFG3 0x0c /* Configure register 3 */ +#define DAT 0x10 /* Data register */ +#define DRV0 0x14 /* Multi-driving register 0 */ +#define DRV1 0x18 /* Multi-driving register 1 */ +#define PUL0 0x1c /* Pull register 0 */ +#define PUL1 0x20 /* Pull register 1 */ + +/* Port n configure register 0 */ +#define GPIO_Pn_CFG0(n) (n*PORT_STRIDE + CFG0) +/* Port n configure register 1 */ +#define GPIO_Pn_CFG1(n) (n*PORT_STRIDE + CFG1) +/* Port n configure register 2 */ +#define GPIO_Pn_CFG2(n) (n*PORT_STRIDE + CFG2) +/* Port n configure register 3 */ +#define GPIO_Pn_CFG3(n) (n*PORT_STRIDE + CFG3) +/* Port n data register */ +#define GPIO_Pn_DAT(n) (n*PORT_STRIDE + DAT) +/* Port n Multi-driving register 0 */ +#define GPIO_Pn_DRV0(n) (n*PORT_STRIDE + DRV0) +/* Port n Multi-driving register 1 */ +#define GPIO_Pn_DRV1(n) (n*PORT_STRIDE + DRV1) +/* Port n Pull register 0 */ +#define GPIO_Pn_PUL0(n) (n*PORT_STRIDE + PUL0) +/* Port n Pull register 1 */ +#define GPIO_Pn_PUL1(n) (n*PORT_STRIDE + PUL1) +/* PIO interrupt configure register 0 */ +#define GPIO_INT_CFG0 0x200 +/* PIO interrupt configure register 1 */ +#define GPIO_INT_CFG1 0x204 +/* PIO interrupt configure register 2 */ +#define GPIO_INT_CFG2 0x208 +/* PIO interrupt configure register 3 */ +#define GPIO_INT_CFG3 0x20c +/* PIO interrupt control register */ +#define GPIO_INT_CTL 0x210 +/* PIO interrupt status register */ +#define GPIO_INT_STA 0x214 +/* PIO interrupt debounce register */ +#define GPIO_INT_DEB 0x218 +/* SDRAM Pad Multi-driving register */ +#define SDR_PAD_DRV 0x220 +/* SDRAM Pad Pull register */ +#define SDR_PAD_PUL 0x224 + +typedef enum AWGPIOLevel { + AW_GPIO_LEVEL_LOW =3D 0, + AW_GPIO_LEVEL_HIGH =3D 1, +} AWGPIOLevel; + +typedef enum AWGPIOCfg { + AW_GPIO_CFG_IN =3D 0, + AW_GPIO_CFG_OUT =3D 1, + AW_GPIO_CFG_FUN =3D 2, +} AWGPIOCfg; + +typedef enum AWGPIOIrqCfg { + AW_GPIO_IRQ_CFG_RISING_EDGE =3D 0, + AW_GPIO_IRQ_CFG_FALLING_EDGE =3D 1, + AW_GPIO_IRQ_CFG_HIGH_LEVEL =3D 2, + AW_GPIO_IRQ_CFG_LOW_LEVEL =3D 3, + AW_GPIO_IRQ_CFG_BOTH_EDGE =3D 4 +} AWGPIOIrqCfg; + +static const char *const port_name_table[] =3D { + "PA", "PB", "PC", "PD", "PE", "PF", "PG", "PH", "PI" +}; + +static const char *portname(unsigned index) +{ + return index < AW_GPIO_PORTS_NUM ? port_name_table[index] : "?"; +} + +static inline char *portname_in(int port) +{ + return g_strconcat(portname(port), "_IN", NULL); +} + +static inline char *portname_out(int port) +{ + return g_strconcat(portname(port), "_OUT", NULL); +} + +static int irq_nr(int port, int line) +{ + int irq =3D -1; + + switch (port) { + case GPIO_PH: + if (line <=3D 21) { + irq =3D line; + } + break; + case GPIO_PI: + if ((line >=3D 10) && (line <=3D 19)) { + irq =3D line + 12; + } + break; + default: + break; + } + return irq; +} + + +#endif /* ALLWINNER_GPIO_REGS_H */ diff --git a/include/hw/gpio/allwinner-gpio.h b/include/hw/gpio/allwinner-g= pio.h new file mode 100644 index 0000000000..6568116330 --- /dev/null +++ b/include/hw/gpio/allwinner-gpio.h @@ -0,0 +1,41 @@ +/* + * Allwinner GPIO registers definition + * + * Copyright (C) 2026 Strahinja Jankovic. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ALLWINNER_GPIO_H +#define ALLWINNER_GPIO_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +/** Size of register I/O address space used by GPIO device */ +#define AW_GPIO_IOSIZE 0x400 + +/** Total number of known registers */ +#define AW_GPIO_REGS_NUM (AW_GPIO_IOSIZE / sizeof(uint32_t)) + +/** Max number of ports */ +#define AW_GPIO_PORTS_NUM 9 +/** Max number of pins per ports */ +#define AW_GPIO_PIN_COUNT 32 + +#define TYPE_AW_GPIO "allwinner.gpio" +OBJECT_DECLARE_SIMPLE_TYPE(AWGPIOState, AW_GPIO) + +struct AWGPIOState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion iomem; + qemu_irq irq; + qemu_irq output[AW_GPIO_PORTS_NUM][AW_GPIO_PIN_COUNT]; + + uint32_t regs[AW_GPIO_REGS_NUM]; +}; + +#endif /* ALLWINNER_GPIO_H */ --=20 2.55.0 From nobody Sat Sep 26 20:50:55 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; 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=fail(p=none dis=none) header.from=gmail.com Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 17895041384471011.2004253660579; Tue, 15 Sep 2026 13:28:58 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x6ZkR-0006iP-RA; Tue, 15 Sep 2026 16:27:23 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x6ZkP-0006h9-6U for qemu-devel@nongnu.org; Tue, 15 Sep 2026 16:27:21 -0400 Received: from mail-lr2-x10.google.com ([2a00:1450:4864:38::10]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1x6ZkN-0004oE-A1 for qemu-devel@nongnu.org; Tue, 15 Sep 2026 16:27:20 -0400 Received: by mail-lr2-x10.google.com with SMTP id 38308e7fff4ca-3a5d4688decso2337711fa.2 for ; Tue, 15 Sep 2026 13:27:18 -0700 (PDT) Received: from [192.168.31.141] (213-65-111-204-no600.tbcn.telia.com. [213.65.111.204]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-3a5daf0dbacsm1621391fa.4.2026.09.15.13.27.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Sep 2026 13:27:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789504038; x=1790108838; darn=nongnu.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:sender:from:to:cc :subject:date:message-id:reply-to:content-type; bh=0HzlsMupkT43STyFi+zWkNuXYuyJLKJmneYJJfz5S2w=; b=AiINi523euK6LKiWu9oSgscR0IyDc7XABSvKTeB3rRKqdbZrhURjvSbKvqnYOiSuZ1 vFIfp6yEvXzTpVHj+jnKIKfo5E/6AVDF389kU1HzHOrci3rxv4c5U7VHPzmzvIGTJm2c bDf4cDAtIIYwElNuYUpREvADl/tnAc1Dc2HSCokT6jTIQtapUGcrK5h5jyfmloWNkWsr hrsJkIgezk/th8NtiNKHDGST6QE40fd0lauuuFdVYWpQwYTIC8RzC7dE/t7PKUnnJiNl dJnYx9d+0d6FC6MIBZ7WZ/D+8raG9Nm9h+1wAvqlJo8+rJNfb6r2DLSo8s6RE5OCs/BN fJFA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789504038; x=1790108838; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:sender:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=0HzlsMupkT43STyFi+zWkNuXYuyJLKJmneYJJfz5S2w=; b=qyYkZ9dqquR+PocX0tgL1e1g35P/GI+Z/aL0Uoclwt/8hOhSCu3V0sZci4NE318XoR yehlEBbtJruKFLuNjJIHtNtQUpaNagaoYOv8aqqAiryuYki0/5Jl1orAyMlTRIzESgQA 7NMhE8U+i5qlNMDy2oiuAhc/NtTQkqv/pEjWZcZJkUmPqHYKrZCbPyN/f1fBmul40tAH I99181ycWKiZT/VlfnpDdKeLAFLfwoduLPn5VOwQYexiyWYkHHOlvf38Ns3Md34PGksR qmXd+QjU94mfb8UUQi6W9HQ0OYDzU0jac6htW6yuLkS75J3Xr78BxWMhaUUxLhyuY0PS I+rw== X-Gm-Message-State: AFuF++m8PSEAUZ4bzAyIiAKISEwh5cEKan7xE70VCJpred82o55mu8mZ ySrUulgAWdt63GU68act8ndY51Bv/fLtH4GYm0L9SJp9eEByXJ8N27sk X-Gm-Gg: AYBFou12tyw6yhN8USANPIvocsPOU6CkJaAf8H5OpXL9rLykm5sPa3CIbjRqS7y4OF7 cuHGTg0Ix/EeTHsvkRRSMHNimfP7nghGVdiDz5eMDIVPWZ2VAqFlMy3nWqutTGG+oOtyuMgvD3p 4u6jCiSwIFpvJBQE7/NOUvTDquL0mxdBQAbNaxW2UHnVwReAW1wK1xE3OUtJyxxscXXb7ZT0L+i /qFuhz1hmldBaYcc8iRcaNqc87D1XJUoTcQ2T1gtzpg+6wHyT941WvaSLd7X+qOWetnTPqHm1U+ lZNLvGdF/EMMu6eIZ6Y/E4GJCMPUNQVIJvGuLr+v6liMBwskwB1gqD4tQWCmTTSeys4/YAUyNW4 D+orQU2NfuRSSm/MzOIBv7gGHCw3KyoSHr3xUFAx1hCGOkg9XfGU7gwN+BkE7N1mUliKOv3MjFv lF0rWNgWLoEq3R2ZoI1kAJUuqW24Vvqh9wQA59Y95OgWkjDyEq3/VmMYxlLsFpQ9xZMxLAvQY1v KMrKU3ExygHC8IR+S2zFL7z2wjsPy3r06aOybseEtKffGBO4CzVPPKWsFc= X-Received: by 2002:a05:651c:1503:b0:3a3:7682:499f with SMTP id 38308e7fff4ca-3a5dca188e5mr143481fa.20.1789504037359; Tue, 15 Sep 2026 13:27:17 -0700 (PDT) From: Strahinja Jankovic Date: Tue, 15 Sep 2026 22:27:05 +0200 Subject: [PATCH v4 2/3] hw/arm: Add GPIO to Allwinner A10 model MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260915-allwinner-gpio-v4-2-b1ee1d09c9fd@gmail.com> References: <20260915-allwinner-gpio-v4-0-b1ee1d09c9fd@gmail.com> In-Reply-To: <20260915-allwinner-gpio-v4-0-b1ee1d09c9fd@gmail.com> To: qemu-devel@nongnu.org Cc: Paolo Bonzini , Peter Maydell , qemu-arm@nongnu.org, Pierrick Bouvier , Fabiano Rosas , Laurent Vivier , Jack wang <163wangjack@gmail.com>, Strahinja Jankovic X-Mailer: b4 0.14.3 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=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:38::10; envelope-from=strahinja.p.jankovic@gmail.com; helo=mail-lr2-x10.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, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-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 development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1789504139627158500 The Allwinner A10 GPIO is added to the Allwinner A10 description, so it is available when Cubieboard is emulated. Update the documentation for Cubieboard to indicate GPIO availability. Signed-off-by: Strahinja Jankovic Reviewed-by: Peter Maydell --- docs/system/arm/cubieboard.rst | 1 + hw/arm/Kconfig | 1 + hw/arm/allwinner-a10.c | 8 ++++++++ include/hw/arm/allwinner-a10.h | 2 ++ 4 files changed, 12 insertions(+) diff --git a/docs/system/arm/cubieboard.rst b/docs/system/arm/cubieboard.rst index 90d24c73a1..ee9f489ead 100644 --- a/docs/system/arm/cubieboard.rst +++ b/docs/system/arm/cubieboard.rst @@ -12,6 +12,7 @@ Emulated devices: - RTC - EMAC - SDHCI +- GPIO - USB controller - SATA controller - TWI (I2C) controller diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index eae06369b0..4445f16f45 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -295,6 +295,7 @@ config ALLWINNER_A10 select ALLWINNER_EMAC select ALLWINNER_I2C select ALLWINNER_A10_SPI + select ALLWINNER_GPIO select AXP2XX_PMU select SERIAL_MM select UNIMP diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c index 66f30757dd..66fe484ae0 100644 --- a/hw/arm/allwinner-a10.c +++ b/hw/arm/allwinner-a10.c @@ -41,6 +41,7 @@ #define AW_A10_EHCI_BASE 0x01c14000 #define AW_A10_OHCI_BASE 0x01c14400 #define AW_A10_SATA_BASE 0x01c18000 +#define AW_A10_GPIO_BASE 0x01c20800 #define AW_A10_WDT_BASE 0x01c20c90 #define AW_A10_RTC_BASE 0x01c20d00 #define AW_A10_I2C0_BASE 0x01c2ac00 @@ -83,6 +84,8 @@ static void aw_a10_init(Object *obj) =20 object_initialize_child(obj, "spi0", &s->spi0, TYPE_AW_A10_SPI); =20 + object_initialize_child(obj, "gpio", &s->gpio, TYPE_AW_GPIO); + for (size_t i =3D 0; i < AW_A10_NUM_USB; i++) { object_initialize_child(obj, "ehci[*]", &s->ehci[i], TYPE_PLATFORM_EHCI); @@ -203,6 +206,11 @@ static void aw_a10_realize(DeviceState *dev, Error **e= rrp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi0), 0, AW_A10_SPI0_BASE); sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi0), 0, qdev_get_gpio_in(dev, = 10)); =20 + /* GPIO */ + sysbus_realize(SYS_BUS_DEVICE(&s->gpio), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio), 0, AW_A10_GPIO_BASE); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio), 0, qdev_get_gpio_in(dev, = 28)); + /* WDT */ sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &error_fatal); sysbus_mmio_map_overlap(SYS_BUS_DEVICE(&s->wdt), 0, AW_A10_WDT_BASE, 1= ); diff --git a/include/hw/arm/allwinner-a10.h b/include/hw/arm/allwinner-a10.h index 445ba1be21..8b7736c7bd 100644 --- a/include/hw/arm/allwinner-a10.h +++ b/include/hw/arm/allwinner-a10.h @@ -13,6 +13,7 @@ #include "hw/misc/allwinner-a10-dramc.h" #include "hw/i2c/allwinner-i2c.h" #include "hw/ssi/allwinner-a10-spi.h" +#include "hw/gpio/allwinner-gpio.h" #include "hw/watchdog/allwinner-wdt.h" #include "system/block-backend.h" =20 @@ -42,6 +43,7 @@ struct AwA10State { AwSdHostState mmc0; AWI2CState i2c0; AWA10SPIState spi0; + AWGPIOState gpio; AwRtcState rtc; AwWdtState wdt; MemoryRegion sram_a; --=20 2.55.0 From nobody Sat Sep 26 20:50:55 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; 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=fail(p=none dis=none) header.from=gmail.com Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1789504132340110.41727298170167; Tue, 15 Sep 2026 13:28:52 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x6ZkX-0006jm-7W; Tue, 15 Sep 2026 16:27:29 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x6ZkT-0006iu-Jp for qemu-devel@nongnu.org; Tue, 15 Sep 2026 16:27:26 -0400 Received: from mail-lr2-x10.google.com ([2a00:1450:4864:38::10]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1x6ZkQ-0004ot-7J for qemu-devel@nongnu.org; Tue, 15 Sep 2026 16:27:24 -0400 Received: by mail-lr2-x10.google.com with SMTP id 38308e7fff4ca-3a302c23fb2so2008101fa.2 for ; Tue, 15 Sep 2026 13:27:21 -0700 (PDT) Received: from [192.168.31.141] (213-65-111-204-no600.tbcn.telia.com. [213.65.111.204]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-3a5daf0dbacsm1621391fa.4.2026.09.15.13.27.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Sep 2026 13:27:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789504041; x=1790108841; darn=nongnu.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:sender:from:to:cc :subject:date:message-id:reply-to:content-type; bh=tYmp6FJA1VtVZLQXUE2onzYmhLAFsyc6jZ5UKULWA6Q=; b=E3A5/umEnf3ZTGEtz2c2PlCtykHp35qSusI1S4s4Q9cSm9Vv2/w/RysuLwiuAK8FRS 06x1CP8+1eVYFITl4fGko86Vk/4shzDE6R9YbKm/MnI1iRXJtHXxGZZFjZQ8o+FCVBS9 RBTJCA0aYOI/TcKdyGgDESUL3Y9YM56KgEcZL6CU1duAGr9CV2DkrAM1kGFYcDLfD56X UHx0MrJ32A1elrFZAaa8CwDHSSX5TlhzR8rlODfKA976/6YkwRF8NvQbPDDYj8I7Pw2l ljLT+ZsuZiLEc15fJI0lnsdYtxoZBwaCnGp1Zyu5o72n49LliUARHCRGv+EgCq15Iy0e VJyQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789504041; x=1790108841; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:sender:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=tYmp6FJA1VtVZLQXUE2onzYmhLAFsyc6jZ5UKULWA6Q=; b=h9qS/hkbY5mGE/tJLT3LV4oo7rhj0agF3hcLEaxYo8ObTRiVWKmj5wzu4SD0EO4kKW gperyaUxqJOzo+hoMpo/Z0zV+fy8q7hz1Uz/V+LBAin+tMKPJstoIaR1bVRpny+SLOCf htK7tn/4vUDkFBdFoFNar4OIxF5bhqTuivDFe+13Ogqi8dTkRRDXyESmkRlVxJbXgcV+ t/ONpRydKy++ORDWBHc2iWOef049gj/YdW+QdV45aYq2W0a9tw8t0EL3THuIBSHBZ3wC vsUQvfrj0ao7/y3Pbn05/HSuorrMHSMui70uJqKwYfFR3XGCcD+yATY/0cOErj9orTHO HPVA== X-Gm-Message-State: AFuF++ksQ5UluePVHWZOKMXwd4gAiCFnQSfb8KzjI6wpXuBV3vXFpMnS mTYljKwy97F2GLmsRxDudvFz7fcGu+5Ayf6g1H8aJE/zRZLM1OjJ8XM8 X-Gm-Gg: AYBFou2V4NXBKA/0TExZ+9yXj9Y0w+ImiNqsZRcXp9gaBoK9CctHAA8m3yoPXe/moOA 4wfoKhTM0JncYcIuPvt5Z6u98WrmE0rqlYGVvW3Twi07jDqy5lRMIeRXZK7KW4TD7q5oJuxdVgN Zs4+HhD+SpgEzSbtnvfI9qMgAWP3rD5X5BZ8uigXqo1pzXK03PVWjc86Qan8ymjsaIc6Sb6uTRP LHH5Z6FE5QgSSX7dV/h1A4PbJ0fk58z2ZEBlybE/MXXiwfE33eMf2ECzPnJvNeIohfRVBYr1/pg wrfRLq+poRIP7UXxEo2lTLLYejavXA7/kd448qh7m/UG0GK8O2OgXH2MiTPhsDnhXbqUsXUvH2b 5LOFhCQ+fbRTEe5R8VdoFojKRBi4Abmof8POSHfbxYs3ypkSTlhN3AuQp345WQRMVJWQjgBsDqB U6Q0Dlyme6RY46aN7q+635F5uKdiV2p/3Pmwnw2knyB01fRg7CL60ioA/AuuwB/QstOOM18hdJ1 M7L4LNWnndV29el2LNIvzV7I35rBejY6fXuit+6zY748gszc4H1k4N9Fak= X-Received: by 2002:a05:651c:1505:b0:3a5:aa00:60cd with SMTP id 38308e7fff4ca-3a5dc9be4a0mr187511fa.8.1789504040336; Tue, 15 Sep 2026 13:27:20 -0700 (PDT) From: Strahinja Jankovic Date: Tue, 15 Sep 2026 22:27:06 +0200 Subject: [PATCH v4 3/3] tests/qtest: Allwinner GPIO tests MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260915-allwinner-gpio-v4-3-b1ee1d09c9fd@gmail.com> References: <20260915-allwinner-gpio-v4-0-b1ee1d09c9fd@gmail.com> In-Reply-To: <20260915-allwinner-gpio-v4-0-b1ee1d09c9fd@gmail.com> To: qemu-devel@nongnu.org Cc: Paolo Bonzini , Peter Maydell , qemu-arm@nongnu.org, Pierrick Bouvier , Fabiano Rosas , Laurent Vivier , Jack wang <163wangjack@gmail.com>, Strahinja Jankovic X-Mailer: b4 0.14.3 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=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:38::10; envelope-from=strahinja.p.jankovic@gmail.com; helo=mail-lr2-x10.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, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-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 development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1789504133622158500 Add Allwinner GPIO set of qtests to validate functionality. Tests cover input and output port setting, as well as interrupt functionality. Signed-off-by: Strahinja Jankovic Reviewed-by: Peter Maydell --- MAINTAINERS | 1 + tests/qtest/allwinner-gpio-test.c | 535 ++++++++++++++++++++++++++++++++++= ++++ tests/qtest/meson.build | 3 +- 3 files changed, 538 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index b453edc820..da6b588eca 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -749,6 +749,7 @@ F: hw/misc/axp2xx.c F: hw/gpio/allwinner-gpio.c F: include/hw/gpio/allwinner-gpio* F: tests/functional/arm/test_cubieboard.py +F: tests/qtest/allwinner-gpio-test.c =20 Allwinner-h3 M: Niek Linnenbank diff --git a/tests/qtest/allwinner-gpio-test.c b/tests/qtest/allwinner-gpio= -test.c new file mode 100644 index 0000000000..c048f0f88b --- /dev/null +++ b/tests/qtest/allwinner-gpio-test.c @@ -0,0 +1,535 @@ +/* + * Allwinner GPIO Tests + * + * Copyright (C) 2026 Strahinja Jankovic + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "libqtest-single.h" + +#include "include/hw/gpio/allwinner-gpio-regs.h" + +#define AW_A10_GPIO_BASE 0x01c20800 +#define AW_A10_IRQ_LINE 28 + +/* Port n configure register 0 */ +#define GPIO_Pn_CFG0_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_CFG0(n)) +/* Port n configure register 1 */ +#define GPIO_Pn_CFG1_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_CFG1(n)) +/* Port n configure register 2 */ +#define GPIO_Pn_CFG2_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_CFG2(n)) +/* Port n configure register 3 */ +#define GPIO_Pn_CFG3_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_CFG3(n)) +/* Port n data register */ +#define GPIO_Pn_DAT_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_DAT(n)) +/* Port n Multi-driving register 0 */ +#define GPIO_Pn_DRV0_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_DRV0(n)) +/* Port n Multi-driving register 1 */ +#define GPIO_Pn_DRV1_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_DRV1(n)) +/* Port n Pull register 0 */ +#define GPIO_Pn_PUL0_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_PUL0(n)) +/* Port n Pull register 1 */ +#define GPIO_Pn_PUL1_ADDR(n) (AW_A10_GPIO_BASE + GPIO_Pn_PUL1(n)) +/* PIO interrupt configure register 0 */ +#define GPIO_INT_CFG0_ADDR (AW_A10_GPIO_BASE + GPIO_INT_CFG0) +/* PIO interrupt configure register 1 */ +#define GPIO_INT_CFG1_ADDR (AW_A10_GPIO_BASE + GPIO_INT_CFG1) +/* PIO interrupt configure register 2 */ +#define GPIO_INT_CFG2_ADDR (AW_A10_GPIO_BASE + GPIO_INT_CFG2) +/* PIO interrupt configure register 3 */ +#define GPIO_INT_CFG3_ADDR (AW_A10_GPIO_BASE + GPIO_INT_CFG3) +/* PIO interrupt control register */ +#define GPIO_INT_CTL_ADDR (AW_A10_GPIO_BASE + GPIO_INT_CTL) +/* PIO interrupt status register */ +#define GPIO_INT_STA_ADDR (AW_A10_GPIO_BASE + GPIO_INT_STA) +/* PIO interrupt debounce register */ +#define GPIO_INT_DEB_ADDR (AW_A10_GPIO_BASE + GPIO_INT_DEB) +/* SDRAM Pad Multi-driving register */ +#define SDR_PAD_DRV_ADDR (AW_A10_GPIO_BASE + SDR_PAD_DRV) +/* SDRAM Pad Pull register */ +#define SDR_PAD_PUL_ADDR (AW_A10_GPIO_BASE + SDR_PAD_PUL) + +static inline uint32_t read_cfg_addr(QTestState *s, int port, int pin) +{ + uint32_t addr =3D 0; + + if (pin >=3D 24) { + addr =3D GPIO_Pn_CFG3_ADDR(port); + } else if (pin >=3D 16) { + addr =3D GPIO_Pn_CFG2_ADDR(port); + } else if (pin >=3D 8) { + addr =3D GPIO_Pn_CFG1_ADDR(port); + } else { + addr =3D GPIO_Pn_CFG0_ADDR(port); + } + return addr; +} + +static inline uint32_t read_int_cfg_addr(QTestState *s, int irq) +{ + uint32_t addr =3D 0; + + if (irq >=3D 24) { + addr =3D GPIO_INT_CFG3_ADDR; + } else if (irq >=3D 16) { + addr =3D GPIO_INT_CFG2_ADDR; + } else if (irq >=3D 8) { + addr =3D GPIO_INT_CFG1_ADDR; + } else { + addr =3D GPIO_INT_CFG0_ADDR; + } + return addr; +} + +static void update_cfg_reg(QTestState *s, int port, int pin, AWGPIOCfg val) +{ + uint32_t addr =3D read_cfg_addr(s, port, pin); + uint32_t cfg =3D qtest_readl(s, addr); + uint32_t offset =3D (pin % CFG_PINS_PER_REG) * CFG_PIN_STRIDE; + + cfg =3D deposit32(cfg, offset, CFG_PIN_STRIDE - 1, val); + qtest_writel(s, addr, cfg); +} + +static void assert_cfg_regval(QTestState *s, int port, int pin, + AWGPIOCfg pin_choice) +{ + uint32_t addr =3D read_cfg_addr(s, port, pin); + uint32_t cfg =3D qtest_readl(s, addr); + uint32_t offset =3D (pin % CFG_PINS_PER_REG) * CFG_PIN_STRIDE; + + g_assert_cmphex(extract32(cfg, + offset, CFG_PIN_STRIDE - 1), =3D=3D, pin_cho= ice); +} + +static void update_data_reg(QTestState *s, int port, int pin, int val) +{ + uint32_t addr =3D GPIO_Pn_DAT_ADDR(port); + uint32_t data =3D qtest_readl(s, addr); + + data =3D deposit32(data, pin, 1, val); + qtest_writel(s, addr, data); +} + +static void assert_data_reg(QTestState *s, int port, int pin, int val) +{ + uint32_t addr =3D GPIO_Pn_DAT_ADDR(port); + uint32_t data =3D qtest_readl(s, addr); + + g_assert_cmphex(extract32(data, pin, 1), =3D=3D, val); +} + +static void update_int_cfg_reg(QTestState *s, int irq, AWGPIOIrqCfg val) +{ + uint32_t addr =3D read_int_cfg_addr(s, irq); + uint32_t cfg =3D qtest_readl(s, addr); + uint32_t offset =3D (irq % INT_CFG_IRQ_PER_REG) * INT_CFG_IRQ_STRIDE; + + cfg =3D deposit32(cfg, offset, INT_CFG_IRQ_STRIDE - 1, val); + qtest_writel(s, addr, cfg); +} + +static void assert_int_cfg_regval(QTestState *s, int irq, AWGPIOIrqCfg irq= _cfg) +{ + uint32_t addr =3D read_int_cfg_addr(s, irq); + uint32_t cfg =3D qtest_readl(s, addr); + uint32_t offset =3D (irq % INT_CFG_IRQ_PER_REG) * INT_CFG_IRQ_STRIDE; + + g_assert_cmphex(extract32(cfg, + offset, INT_CFG_IRQ_STRIDE - 1), =3D=3D, irq= _cfg); +} + +static void test_reset_values(void) +{ + QTestState *s =3D qtest_init("-machine cubieboard"); + + for (int port =3D 0; port < AW_GPIO_PORTS_NUM; port++) { + g_assert_cmphex(qtest_readl(s, + GPIO_Pn_DRV0_ADDR(port)), =3D=3D, + aw_gpio_port_reset[port].drv[0]); + g_assert_cmphex(qtest_readl(s, + GPIO_Pn_DRV1_ADDR(port)), =3D=3D, + aw_gpio_port_reset[port].drv[1]); + g_assert_cmphex(qtest_readl(s, + GPIO_Pn_PUL0_ADDR(port)), =3D=3D, + aw_gpio_port_reset[port].pul[0]); + g_assert_cmphex(qtest_readl(s, + GPIO_Pn_PUL1_ADDR(port)), =3D=3D, + aw_gpio_port_reset[port].pul[1]); + } + + qtest_quit(s); +} + +typedef struct { + int port; + int pin; +} PortPinTestParam; + +const PortPinTestParam in_out_test_parameters[] =3D { + { GPIO_PA, 0 }, + { GPIO_PA, 17 }, + { GPIO_PB, 19 }, + { GPIO_PC, 23 }, + { GPIO_PD, 26 }, + { GPIO_PE, 11 }, + { GPIO_PF, 5 }, + { GPIO_PG, 10 }, + { GPIO_PH, 16 }, + { GPIO_PI, 21 }, +}; + +const PortPinTestParam irq_test_parameters[] =3D { + { GPIO_PH, 1 }, + { GPIO_PH, 10 }, + { GPIO_PH, 21 }, + { GPIO_PI, 10 }, + { GPIO_PI, 19 }, +}; + +/* Set pin to output, check that it propagates */ +static void test_set_output_pins(const void *data) +{ + PortPinTestParam *param =3D (PortPinTestParam *)data; + g_autofree char *out_name =3D portname_out(param->port); + + QTestState *s =3D qtest_init("-machine cubieboard"); + qtest_irq_intercept_out_named(s, "/machine/soc/gpio", out_name); + + /* Configure pin as output */ + update_cfg_reg(s, param->port, param->pin, AW_GPIO_CFG_OUT); + /* Set value in register */ + update_data_reg(s, param->port, param->pin, AW_GPIO_LEVEL_HIGH); + + /* Check that the output pin is high */ + g_assert_true(qtest_get_irq(s, param->pin)); + + /* Cleanup */ + /* Set value in register */ + update_data_reg(s, param->port, param->pin, AW_GPIO_LEVEL_LOW); + /* Configure pin as input */ + update_cfg_reg(s, param->port, param->pin, AW_GPIO_CFG_IN); + + qtest_quit(s); +} + +/* + * Set pin to input, check that the value is written but it does not propa= gate + */ +static void test_set_input_pins(const void *data) +{ + PortPinTestParam *param =3D (PortPinTestParam *)data; + g_autofree char *out_name =3D portname_out(param->port); + + QTestState *s =3D qtest_init("-machine cubieboard"); + + qtest_irq_intercept_out_named(s, "/machine/soc/gpio", out_name); + + /* Configure pin as input */ + update_cfg_reg(s, param->port, param->pin, AW_GPIO_CFG_IN); + /* Set value in register */ + update_data_reg(s, param->port, param->pin, AW_GPIO_LEVEL_HIGH); + /* Check that the output pin is still low */ + g_assert_false(qtest_get_irq(s, param->pin)); + + qtest_quit(s); +} + +/* Test rising edge interrupt */ +static void test_irq_pin_rising(const void *data) +{ + PortPinTestParam *param =3D (PortPinTestParam *)data; + QTestState *s =3D qtest_init("-machine cubieboard"); + const int gpio_port =3D param->port; + const int port_pin =3D param->pin; + g_autofree char *in_name =3D portname_in(gpio_port); + int irq_pin =3D irq_nr(gpio_port, port_pin); + unsigned int irq_mask =3D 1u << irq_pin; + + qtest_irq_intercept_in(s, "/machine/soc"); + + /* Configure as input and set pin low */ + update_cfg_reg(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + assert_cfg_regval(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_LOW); + + /* Enable interrupt */ + update_int_cfg_reg(s, irq_pin, AW_GPIO_IRQ_CFG_RISING_EDGE); + assert_int_cfg_regval(s, irq_pin, AW_GPIO_IRQ_CFG_RISING_EDGE); + qtest_writel(s, GPIO_INT_CTL_ADDR, irq_mask); + + /* Raise interrupt */ + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_HIGH); + /* Check that value is written */ + assert_data_reg(s, gpio_port, port_pin, AW_GPIO_LEVEL_HIGH); + /* Check interrupt status */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), =3D=3D, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Clear interrupt */ + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + + /* Check that IRQ line is low */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR) & irq_mask, =3D=3D, = 0); + g_assert_false(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + qtest_quit(s); +} + +/* Test falling edge interrupt */ +static void test_irq_pin_falling(const void *data) +{ + PortPinTestParam *param =3D (PortPinTestParam *)data; + QTestState *s =3D qtest_init("-machine cubieboard"); + const int gpio_port =3D param->port; + const int port_pin =3D param->pin; + g_autofree char *in_name =3D portname_in(gpio_port); + int irq_pin =3D irq_nr(gpio_port, port_pin); + unsigned int irq_mask =3D 1u << irq_pin; + + qtest_irq_intercept_in(s, "/machine/soc"); + + /* Configure as input and set pin high */ + update_cfg_reg(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + assert_cfg_regval(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_HIGH); + + /* Enable interrupt */ + update_int_cfg_reg(s, irq_pin, AW_GPIO_IRQ_CFG_FALLING_EDGE); + assert_int_cfg_regval(s, irq_pin, AW_GPIO_IRQ_CFG_FALLING_EDGE); + qtest_writel(s, GPIO_INT_CTL_ADDR, irq_mask); + + /* Raise interrupt */ + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_LOW); + /* Check that value is written */ + assert_data_reg(s, gpio_port, port_pin, AW_GPIO_LEVEL_LOW); + /* Check interrupt status */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), =3D=3D, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Clear interrupt */ + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + + /* Check that IRQ line is low */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR) & irq_mask, =3D=3D, = 0); + g_assert_false(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + qtest_quit(s); +} + +/* Test high level interrupt */ +static void test_irq_pin_high_level(const void *data) +{ + PortPinTestParam *param =3D (PortPinTestParam *)data; + QTestState *s =3D qtest_init("-machine cubieboard"); + const int gpio_port =3D param->port; + const int port_pin =3D param->pin; + g_autofree char *in_name =3D portname_in(gpio_port); + int irq_pin =3D irq_nr(gpio_port, port_pin); + unsigned int irq_mask =3D 1u << irq_pin; + + qtest_irq_intercept_in(s, "/machine/soc"); + + /* Configure as input and set pin low */ + update_cfg_reg(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + assert_cfg_regval(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_LOW); + + /* Enable interrupt */ + update_int_cfg_reg(s, irq_pin, AW_GPIO_IRQ_CFG_HIGH_LEVEL); + assert_int_cfg_regval(s, irq_pin, AW_GPIO_IRQ_CFG_HIGH_LEVEL); + qtest_writel(s, GPIO_INT_CTL_ADDR, irq_mask); + + /* Raise interrupt */ + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_HIGH); + /* Check that value is written */ + assert_data_reg(s, gpio_port, port_pin, AW_GPIO_LEVEL_HIGH); + /* Check interrupt status */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), =3D=3D, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Try clearing interrupt, it should still be asserted */ + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), =3D=3D, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Set line low and clear interrupt, it should be cleared now */ + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_LOW); + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + + /* Check that IRQ line is low */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR) & irq_mask, =3D=3D, = 0); + g_assert_false(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + qtest_quit(s); +} + +/* Test low level interrupt */ +static void test_irq_pin_low_level(const void *data) +{ + PortPinTestParam *param =3D (PortPinTestParam *)data; + QTestState *s =3D qtest_init("-machine cubieboard"); + const int gpio_port =3D param->port; + const int port_pin =3D param->pin; + g_autofree char *in_name =3D portname_in(gpio_port); + int irq_pin =3D irq_nr(gpio_port, port_pin); + unsigned int irq_mask =3D 1u << irq_pin; + + qtest_irq_intercept_in(s, "/machine/soc"); + + /* Configure as input and set pin high */ + update_cfg_reg(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + assert_cfg_regval(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_HIGH); + + /* Enable interrupt */ + update_int_cfg_reg(s, irq_pin, AW_GPIO_IRQ_CFG_LOW_LEVEL); + assert_int_cfg_regval(s, irq_pin, AW_GPIO_IRQ_CFG_LOW_LEVEL); + qtest_writel(s, GPIO_INT_CTL_ADDR, irq_mask); + + /* Raise interrupt */ + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_LOW); + /* Check that value is written */ + assert_data_reg(s, gpio_port, port_pin, AW_GPIO_LEVEL_LOW); + /* Check interrupt status */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), =3D=3D, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Try clearing interrupt, it should still be asserted */ + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), =3D=3D, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Set line high and clear interrupt, it should be cleared now */ + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_HIGH); + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + + /* Check that IRQ line is low */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR) & irq_mask, =3D=3D, = 0); + g_assert_false(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + qtest_quit(s); +} + +/* Test both edge interrupt */ +static void test_irq_pin_both_edge(const void *data) +{ + PortPinTestParam *param =3D (PortPinTestParam *)data; + QTestState *s =3D qtest_init("-machine cubieboard"); + const int gpio_port =3D param->port; + const int port_pin =3D param->pin; + g_autofree char *in_name =3D portname_in(gpio_port); + int irq_pin =3D irq_nr(gpio_port, port_pin); + unsigned int irq_mask =3D 1u << irq_pin; + + qtest_irq_intercept_in(s, "/machine/soc"); + + /* Configure as input and set pin low */ + update_cfg_reg(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + assert_cfg_regval(s, gpio_port, port_pin, AW_GPIO_CFG_IN); + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_LOW); + + /* Enable interrupt */ + update_int_cfg_reg(s, irq_pin, AW_GPIO_IRQ_CFG_BOTH_EDGE); + assert_int_cfg_regval(s, irq_pin, AW_GPIO_IRQ_CFG_BOTH_EDGE); + qtest_writel(s, GPIO_INT_CTL_ADDR, irq_mask); + + /* Raise interrupt - rising edge */ + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_HIGH); + /* Check that value is written */ + assert_data_reg(s, gpio_port, port_pin, AW_GPIO_LEVEL_HIGH); + /* Check interrupt status */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), =3D=3D, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Clear interrupt */ + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + + /* Check that IRQ line is low */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR) & irq_mask, =3D=3D, = 0); + g_assert_false(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Raise interrupt - falling edge */ + qtest_set_irq_in(s, + "/machine/soc/gpio", in_name, port_pin, + AW_GPIO_LEVEL_LOW); + /* Check that value is written */ + assert_data_reg(s, gpio_port, port_pin, AW_GPIO_LEVEL_LOW); + /* Check interrupt status */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR), =3D=3D, irq_mask); + g_assert_true(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + /* Clear interrupt */ + qtest_writel(s, GPIO_INT_STA_ADDR, irq_mask); + + /* Check that IRQ line is low */ + g_assert_cmphex(qtest_readl(s, GPIO_INT_STA_ADDR) & irq_mask, =3D=3D, = 0); + g_assert_false(qtest_get_irq(s, AW_A10_IRQ_LINE)); + + qtest_quit(s); +} + + +static void add_test(const char *type, const PortPinTestParam *p, + void (*testfn)(const void *)) +{ + g_autofree char *name =3D + g_strdup_printf("/allwinner-cubieboard/gpio/%s/%s%d", + type, portname(p->port), p->pin); + + qtest_add_data_func(name, p, testfn); +} + + +int main(int argc, char **argv) +{ + int r; + + g_test_init(&argc, &argv, NULL); + + for (int i =3D 0; i < ARRAY_SIZE(in_out_test_parameters); i++) { + const PortPinTestParam *p =3D &in_out_test_parameters[i]; + add_test("set_output_pins", p, test_set_output_pins); + add_test("set_input_pins", p, test_set_input_pins); + } + qtest_add_func("/allwinner-cubieboard/gpio/reset_values", + test_reset_values); + for (int i =3D 0; i < ARRAY_SIZE(irq_test_parameters); i++) { + const PortPinTestParam *p =3D &irq_test_parameters[i]; + add_test("irq_pin_rising", p, test_irq_pin_rising); + add_test("irq_pin_falling", p, test_irq_pin_falling); + add_test("irq_pin_high_level", p, test_irq_pin_high_level); + add_test("irq_pin_low_level", p, test_irq_pin_low_level); + add_test("irq_pin_both_edge", p, test_irq_pin_both_edge); + } + r =3D g_test_run(); + + return r; +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index c3593f7530..6c5db1a0a3 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -268,7 +268,8 @@ qtests_arm =3D \ (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and config_all_devices.has_key('CONFIG_DM163')? ['dm163-test'] : []) + \ ['arm-cpu-features', - 'boot-serial-test'] + 'boot-serial-test'] + \ + (config_all_devices.has_key('CONFIG_ALLWINNER_A10')? ['allwinner-gpio-te= st'] : []) =20 # TODO: once aarch64 TCG is fixed on ARM 32 bit host, make bios-tables-tes= t unconditional qtests_aarch64 =3D \ --=20 2.55.0