From nobody Mon Sep 28 00:03:03 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 1788008218695405.8597241833178; Sat, 29 Aug 2026 05:56:58 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0IbU-0001YR-Px; Sat, 29 Aug 2026 08:56:12 -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 1x0IbS-0001Wt-Cp for qemu-devel@nongnu.org; Sat, 29 Aug 2026 08:56:10 -0400 Received: from mail-lf1-x12f.google.com ([2a00:1450:4864:20::12f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1x0IbO-00021L-6j for qemu-devel@nongnu.org; Sat, 29 Aug 2026 08:56:10 -0400 Received: by mail-lf1-x12f.google.com with SMTP id 2adb3069b0e04-5b4ab0e4da6so1695790e87.3 for ; Sat, 29 Aug 2026 05:56:05 -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 2adb3069b0e04-5b5e8a0ce42sm968866e87.57.2026.08.29.05.56.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 29 Aug 2026 05:56:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788008164; x=1788612964; 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=oNnj2fLEMYq5Tz8EcYB6SXjMYDv/P85S0ugsV5A0eZ8=; b=YQYZPXbx+HSfUU/5jVKM198NsTLYINsGfKARmYB4QKIp1prvdxCR9c33E197tXku2v xNyvVh/QtfL2tKWJkI3kGPPS81y4355O2yYCYJPv31PGqgTnzlLkkg++5YGB+YeCLPOf +A5ObSj6+phwDRTDVU8DxmzThORUfa6DKK25nxavu7wZNuBIOzKSikXweVlnZbKOgIVm zllVDGEXf6Ci4AZAk/bdTtUTBb5QcqJr+wP/8a425JYyYivOAxkDJdldD69ZE1NscvDK zl5TxqsGOnzOGH48E9Kyg60dJk8mTw4crKmt+vs0WAsSkZt6CraJyZbCid8iSu/GIQlY Bvvw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788008164; x=1788612964; 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=oNnj2fLEMYq5Tz8EcYB6SXjMYDv/P85S0ugsV5A0eZ8=; b=LbBNrIVZfTg6JO/kQO36a7H2VP+9etHiYCIMAqA6Uwj8pXazQEcLR31IDwNdWYKolN et1DQjOkChPphnj1RJBrEPxTV3nhS4Z3CFfwpMFeFfN+BTes67L1+HWjCkJWrv69+saI RjQ7ywkXnbUN8AoSAxEd4uFH4qQFQCd0C6cz8ELJALXEhKJF5cHR8iPRAr0UYtcAlGdu EKcnmrUIHJbA3vfVZRUc/Qdza6lREw3dwTLkXHiVzaUDDsKDDlo4r4u/PsVP5JeZG7/S +Gc1C4NBJ7J2rXT32TjlkN78NIPmXSL8m59WsrPmNykTDSxwlbWpDmtighDq/Vg4xNg+ +V0Q== X-Gm-Message-State: AFuF++kBJD41cSbQtFrr4XC9WjdIv0kKbPB0ZEQ5BTGwD807kGEf/Fcb kutMsPTAxLjEQK6me4Mgyi2yREi2EqNg7FC3boOSzcLEeHbYsq5jOjv+ X-Gm-Gg: AYBFou3+umSeVNYrSpY8gpHzaORxPgmBoyeEk4qZUFcjujGq4ocfl0qM35derhHuL0P Fv5QfZb61+pURVQWa78CGp5d16/JGDUrVQMkkCWiSmKg6/f+Lwd6CYf1rGFFp0HN6aVCxtiIbdC YU56k6NwIa9NiJffjt+FnvmVJmnFZaICh41J1nWgq4dV1l65YHFWZpghqvCtrJTyq7TzJ80vLpJ mKZxVs8w5oGpWOtuXn7DTYnC7ulssOWeTgz60zxpwnRq6KIHD9l1v5HB6r/UjiZEK86vt0rVaVh a2w27T251o2Hup8UHW7wr8wMFqaE0W5tSmbx0rDjKbZGHp1AjueG7I+rT2CajB3I9J4rIuhmooa 8fV4+LIH+lohihi4gC4rPFUmxzrijlKnt/g9FpQSvCtYPdAIAwxQE8bCfGJaCz54Fods4jSiVkU 4EuPzx5IhBRevjPFajWvZCLzSEbudbA+WdU5fj8pJDETPQzLEAFhBnxwS2pYgprGpZSJJ0pxlb/ xr9/Bj84BAmyZqjF1TLZgUKM1RfBKngpncBHYlmGHxHDeDY X-Received: by 2002:a05:6512:1249:b0:5b5:e1b5:e963 with SMTP id 2adb3069b0e04-5b5e68b2100mr4076908e87.2.1788008163947; Sat, 29 Aug 2026 05:56:03 -0700 (PDT) From: Strahinja Jankovic Date: Sat, 29 Aug 2026 14:55:43 +0200 Subject: [PATCH v2 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: <20260829-allwinner-gpio-v2-1-f0ebcb07302e@gmail.com> References: <20260829-allwinner-gpio-v2-0-f0ebcb07302e@gmail.com> In-Reply-To: <20260829-allwinner-gpio-v2-0-f0ebcb07302e@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:20::12f; envelope-from=strahinja.p.jankovic@gmail.com; helo=mail-lf1-x12f.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: 1788008221853154100 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 | 465 ++++++++++++++++++++++++++++++= ++++ hw/gpio/meson.build | 1 + hw/gpio/trace-events | 6 + include/hw/gpio/allwinner-gpio-regs.h | 228 +++++++++++++++++ include/hw/gpio/allwinner-gpio.h | 41 +++ 7 files changed, 746 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index b51f5c3e60..6cefdde70b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -743,6 +743,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..fb0f3f7ca1 --- /dev/null +++ b/hw/gpio/allwinner-gpio.c @@ -0,0 +1,465 @@ +/* + * 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 bool gpio_is_input(AWPortMap *port, uint32_t pin); +static int int_ctl_cfg(AWGPIOState *s, int irq_line); + +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; + int i; + + for (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) +{ + int i; + + for (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 (o->ports[port].dat & ~(1u << line)) | + (aw_level << line); + } + allwinner_gpio_update_int(s); +} + + +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 - 1); +} + +static inline void port_update_output_lines(AWGPIOState *s, uint32_t port) +{ + AWPortsOverlay *o =3D (AWPortsOverlay *)s->regs; + int pin; + + for (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 if (gpio_is_input(&o->ports[port], pin)) { + qemu_irq_lower(s->output[port][pin]); + } + } +} + +static inline void allwinner_gpio_update_all_output_lines(AWGPIOState *s) +{ + for (int port =3D 0; port < AW_GPIO_PORTS_NUM; port++) { + port_update_output_lines(s, port); + } +} + + +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; +} + +/* update output values */ +/* update interrupts */ +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; + int port =3D 0; + + memset(s->regs, 0, AW_GPIO_IOSIZE); + + for (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]; + } + + allwinner_gpio_update_all_output_lines(s); + allwinner_gpio_update_int(s); +} + +static void allwinner_gpio_realize(DeviceState *dev, Error **errp) +{ + AWGPIOState *s =3D AW_GPIO(dev); + int port; + + memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_gpio_ops, s, + TYPE_AW_GPIO, AW_GPIO_IOSIZE); + + for (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..52d72c0f25 --- /dev/null +++ b/include/hw/gpio/allwinner-gpio-regs.h @@ -0,0 +1,228 @@ +/* + * 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, + /* Use only fun for testing */ +} 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..f6778d1893 --- /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 Mon Sep 28 00:03:03 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 1788008218351338.7088276377732; Sat, 29 Aug 2026 05:56:58 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0IbT-0001Xe-Lc; Sat, 29 Aug 2026 08:56:11 -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 1x0IbR-0001WD-6U for qemu-devel@nongnu.org; Sat, 29 Aug 2026 08:56:09 -0400 Received: from mail-lf1-x12b.google.com ([2a00:1450:4864:20::12b]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1x0IbP-00021U-65 for qemu-devel@nongnu.org; Sat, 29 Aug 2026 08:56:08 -0400 Received: by mail-lf1-x12b.google.com with SMTP id 2adb3069b0e04-5b5e1340faeso1726145e87.3 for ; Sat, 29 Aug 2026 05:56:06 -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 2adb3069b0e04-5b5e8a0ce42sm968866e87.57.2026.08.29.05.56.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 29 Aug 2026 05:56:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788008165; x=1788612965; 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=n+bFI7NMOKFyTqZjD2J643gd9nkuw7CEQP5XcBksxKI=; b=MuzSJwlhESODcvR5nPs0HmZ2W+otvkelkG0uu/3RRyRSBVp+J1zN3geryXQ9luBBNp 3VsILVMTqqKa0y2EJGQ+CcacPS6c5PE/nFi/bAiV7e8W7NBexqJmFvn4LmamMKOgr5+s /yzo+ZEjjVyIH0gGuqpiRE35N9xh1TUNSK++Vcm2aPtzevjdRCdo8Lq4tjHE32uAPvS2 V3yGWop+zcDCSfh1kKGyc0d6rGHvaXx96opLfHDv61JQc3PFoWGRfebpTjgJEn1YMKpd rcw9OsC2ishL/CpklT3fHATF2z1bPh6cCwgmqqU1OfDDW7n/0pHcgqehAE62AzD2mh5z Y/UQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788008165; x=1788612965; 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=n+bFI7NMOKFyTqZjD2J643gd9nkuw7CEQP5XcBksxKI=; b=IX47rCnBU8kFerkPLpzbv1VJ4qhqdXoh5XhpACdveAsdJNbGUtRIGIu+NvjCKCBvQF MGMkNEnW4FssnbSOoqEvBQNWMnJCebb21LYOX7xRWAA6ZbGoleDXI1ZvJA+FJm2ensAO nF/wXhqjmFR8WnnzTrifVuDAE0YkUWoar9P1jSRN+swRsxwU4WjcXrUfCfl7NkXlmsYQ H9L8thlp4b4w9jTZ5AfwL4I9AJF1HCvAio/yp8whUASW15ZnctvebUljd4ooFQ4tS9N1 sj3W6iNY/QizVMwO9RZW5XUBiXFVDuFxs/smaoNpSpga4vT3qazOQXiKDzYBlpatZYzx YQlw== X-Gm-Message-State: AFuF++lLsIlMmg3sTJLjaXqwSHFQ40JayKpQUjupTCgcUu3Kvi72JNDt tCNsQfuN3lj5iNZrkVsMMWZgmN6WPHoWslV9mzxlcfvwbAAblUhOMG/YgEbSlv8b X-Gm-Gg: AYBFou1WB+YGz5Rsi6wpYICgb+q2/VFTU8hy8IrNMZCBDKMqyqJN8JB0RXrflSAm3Fm 62wR8rVGwK7xWjDuHW8++INpLH8FoioSq/WRWw8G5DVDBxqVEEmSjkPGOkjE4J8uqguoM8XOoKP iOWdSbkqMDg+0ZW0+Vn9E+viMv61yfYmkhwotKWDDuoJCLphauXQPRMSTgmTt78zQjuAW1hx/Uy RqHOAcpc2M2mHHWW1U2nC/8PbOQCAs5RhbWBmHkcoLiEV5OG22+71vncZlCOFbrjyZdja2WM8fQ Lhz2n9J60Xp+y0ZxFm7aLSzatBjEOgxDuAPZWKlcuKayNrH6TVnUNfTe8g+Th+i4hFLdTX66nHd 4DTQGKWX7aHneY078Eu49CJz7qS7NFBW9L6scTom4HwH5wyse+X3avEUtj40/WZQGNqcm5j16K9 PPtOChXjMOEt+fTq4XoC28izHVHG0KtBdild3NVr+F6htC/6KvRkYHoznLi/E9dyWzfdaISvsQ+ XynyWZ74Gy5z+fDVt1Usf99BiOHWPVNv5YrhvCGwRDHSXuz X-Received: by 2002:a05:6512:3b8f:b0:5b5:e2f1:e6f9 with SMTP id 2adb3069b0e04-5b5e68bc823mr4450450e87.11.1788008164992; Sat, 29 Aug 2026 05:56:04 -0700 (PDT) From: Strahinja Jankovic Date: Sat, 29 Aug 2026 14:55:44 +0200 Subject: [PATCH v2 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: <20260829-allwinner-gpio-v2-2-f0ebcb07302e@gmail.com> References: <20260829-allwinner-gpio-v2-0-f0ebcb07302e@gmail.com> In-Reply-To: <20260829-allwinner-gpio-v2-0-f0ebcb07302e@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:20::12b; envelope-from=strahinja.p.jankovic@gmail.com; helo=mail-lf1-x12b.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: 1788008220747154100 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 260d2f0751..6d326746c9 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -311,6 +311,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 Mon Sep 28 00:03:03 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 1788008239750978.8519648889122; Sat, 29 Aug 2026 05:57:19 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0IbV-0001YY-8y; Sat, 29 Aug 2026 08:56:13 -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 1x0IbT-0001Xg-Pk for qemu-devel@nongnu.org; Sat, 29 Aug 2026 08:56:11 -0400 Received: from mail-lf1-x133.google.com ([2a00:1450:4864:20::133]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1x0IbP-00021l-Tb for qemu-devel@nongnu.org; Sat, 29 Aug 2026 08:56:11 -0400 Received: by mail-lf1-x133.google.com with SMTP id 2adb3069b0e04-5b5e18f0439so1528392e87.2 for ; Sat, 29 Aug 2026 05:56:07 -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 2adb3069b0e04-5b5e8a0ce42sm968866e87.57.2026.08.29.05.56.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 29 Aug 2026 05:56:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788008166; x=1788612966; 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=bYf4AKXFYrO90uxQIj+9shWAp8sC3wns/GFIlvgp1o8=; b=bPGx7qwWUIuzB9mxXDRJNMs+FSJmoBBjH7XCGZmWQZ08fQ5z3K0gN18krOSVEyTyWH 7uTirKehGtTgZTTWXLig64BVPTjVKy49NI9QMOahF/UB3ASEFB92xkwiMPPQGxNyRtcC NtkRLS1MPp7ni0M+v+h6k266fSxORg5p7KYyI6F3Kq+uR6icXOlRpxKIXV3GSXQ/7D7+ Ub4xUerAsPFNYqPDZGuBhBjA+876XIvvjRqU+eE4TVknzpKkJliM2+Z9goR0tBqp3e6s xF3cbeyQXo+yGpfsqY+z7274wsIg8KJ0wJntNxJlw2vL91iCbPcLRADu7gX6Fp436G8P hJAQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788008166; x=1788612966; 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=bYf4AKXFYrO90uxQIj+9shWAp8sC3wns/GFIlvgp1o8=; b=p5Qo3StAjTOufWoceel9zRZl4gHJen4t/Qmxdu8nLawPCOBm1LS0H88OSAqS0DJjaK 0FiMD3u7uSHxnmWr3dk1cb3DdwqT2/disEMPKZUsq1+7L09RDVtkEXKQ5q+fCQdGp+yf SVChTH/NxZg7gTrAbb5+YIArTKJpG692XK8h/LJg+XrhP3+wcgcMxaPGzEr0niU13fsF Kb9JJZyhYd2bwNY/MEL3JiaOO36l1841hPZ4ZIOSaZU2xUyA3RPQS0Q2nHLFA/wdwxuI N1XVlHWESxFDT1s6SMB+bpA2njpRSWbnHBwfW6B+N/3ScHVBwKzPC6icstdZzHcDCyTE 6V8g== X-Gm-Message-State: AFuF++mUK3Hu9Z+BtasiybHMrkBeScP6BaNhqvMVgVEIx0YMvuB8qtNR FwH3bPlY3+11CMZUDPbRI8vrLngL2+6LrMc+VxIUWowRlYFk9raa99KJ X-Gm-Gg: AYBFou3GMgPW5uKivfHs8JfPLDSAXa41vtSe9OebH+bSYkdYqwESJ4xQcmPy1KPWNzL hY9YdtDbcXJ7tEc0Ay91kLr44vqG8RNYbnWdBzJZKP3/vtk1fFi6W6z/AUtKC0ZnEtoq3elQtBU +Ugv55LO26mFBQJoT6Z6qmYl1RuC2Xrbbn8jmi7IWK28by+g8U/+0IBGwlKsj31Oi4E8SE6m61x CfdeX8PBoP9lImt2VRVy0RnZoljWeMdyLKlJOIFJ9SbRipUbrMlIZJhI6soAwQkkKOqA5cFPzyX RJ5erqLikQN7CGx5G3uWSvl8ZlXKhrhu0JfJqrBV38RDOY8pwWTXp3JUat5bjbfza1tuJLJTHrY 86McbtJnbNxsVTMZNkLJOq3gXAi28jqZe3iKAzalbnaUWVWSzSbBd6KuB6JJWA7SBj86sJLKS38 WbPEK+x7j3orNxJDJfp3wYQY0/LSN2g8aioWA2WUfWeKDfDBHm+AVbxmJPqHxV5oVytCDtBnf8Q 9FAqY/oXuUvOLAwnqrz6sEZucEWNO80xDjKpenSB8g1kWOgxVdmaoe1KR0= X-Received: by 2002:a05:6512:4010:b0:5b1:4c6f:92f9 with SMTP id 2adb3069b0e04-5b5e690db15mr4449143e87.20.1788008166025; Sat, 29 Aug 2026 05:56:06 -0700 (PDT) From: Strahinja Jankovic Date: Sat, 29 Aug 2026 14:55:45 +0200 Subject: [PATCH v2 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: <20260829-allwinner-gpio-v2-3-f0ebcb07302e@gmail.com> References: <20260829-allwinner-gpio-v2-0-f0ebcb07302e@gmail.com> In-Reply-To: <20260829-allwinner-gpio-v2-0-f0ebcb07302e@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:20::133; envelope-from=strahinja.p.jankovic@gmail.com; helo=mail-lf1-x133.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: 1788008241360158500 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 --- MAINTAINERS | 1 + tests/qtest/allwinner-gpio-test.c | 538 ++++++++++++++++++++++++++++++++++= ++++ tests/qtest/meson.build | 3 +- 3 files changed, 541 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 6cefdde70b..2691796557 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -746,6 +746,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..10da0a4274 --- /dev/null +++ b/tests/qtest/allwinner-gpio-test.c @@ -0,0 +1,538 @@ +/* + * 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_choice); +} + +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); +} + + +int main(int argc, char **argv) +{ + int r; + + g_test_init(&argc, &argv, NULL); + + for (int i =3D 0; i < G_N_ELEMENTS(in_out_test_parameters); i++) { + g_autofree char *out_name =3D g_strdup_printf( + "/allwinner-cubieboard/gpio/set_output_pins/%s%d", + portname(in_out_test_parameters[i].port), in_out_test_parameters[i].pi= n); + g_autofree char *in_name =3D g_strdup_printf( + "/allwinner-cubieboard/gpio/set_input_pins/%s%d", + portname(in_out_test_parameters[i].port), in_out_test_parameters[i].pi= n); + qtest_add_data_func(out_name, &in_out_test_parameters[i], + test_set_output_pins); + qtest_add_data_func(in_name, &in_out_test_parameters[i], + test_set_input_pins); + } + qtest_add_func("/allwinner-cubieboard/gpio/reset_values", + test_reset_values); + for (int i =3D 0; i < G_N_ELEMENTS(irq_test_parameters); i++) { + g_autofree char *rising_edge_name =3D g_strdup_printf( + "/allwinner-cubieboard/gpio/irq_pin_rising/%s%d", + portname(irq_test_parameters[i].port), irq_test_parameters[i].pin); + qtest_add_data_func( + rising_edge_name, &irq_test_parameters[i], test_irq_pin_rising= ); + g_autofree char *falling_edge_name =3D g_strdup_printf( + "/allwinner-cubieboard/gpio/irq_pin_falling/%s%d", + portname(irq_test_parameters[i].port), irq_test_parameters[i].pin); + qtest_add_data_func( + falling_edge_name, &irq_test_parameters[i], test_irq_pin_falli= ng); + g_autofree char *high_level_name =3D g_strdup_printf( + "/allwinner-cubieboard/gpio/irq_pin_high_level/%s%d", + portname(irq_test_parameters[i].port), irq_test_parameters[i].pin); + qtest_add_data_func( + high_level_name, &irq_test_parameters[i], test_irq_pin_high_le= vel); + g_autofree char *low_level_name =3D g_strdup_printf( + "/allwinner-cubieboard/gpio/irq_pin_low_level/%s%d", + portname(irq_test_parameters[i].port), irq_test_parameters[i].pin); + qtest_add_data_func( + low_level_name, &irq_test_parameters[i], test_irq_pin_low_leve= l); + g_autofree char *both_edge_name =3D g_strdup_printf( + "/allwinner-cubieboard/gpio/irq_pin_both_edge/%s%d", + portname(irq_test_parameters[i].port), irq_test_parameters[i].pin); + qtest_add_data_func( + both_edge_name, &irq_test_parameters[i], test_irq_pin_both_edg= e); + + } + r =3D g_test_run(); + + return r; +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index f7c7d06620..853f7a9c4c 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -257,7 +257,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