From patchwork Wed Dec 31 14:06:06 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Colin Leitner X-Patchwork-Id: 424799 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id BD1A21400B7 for ; Thu, 1 Jan 2015 01:08:20 +1100 (AEDT) Received: from localhost ([::1]:40307 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Y6Jwg-0006oU-LH for incoming@patchwork.ozlabs.org; Wed, 31 Dec 2014 09:08:18 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:36567) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Y6JvM-0004kT-QC for qemu-devel@nongnu.org; Wed, 31 Dec 2014 09:07:02 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Y6JvK-0008Ay-Pl for qemu-devel@nongnu.org; Wed, 31 Dec 2014 09:06:56 -0500 Received: from mail-we0-x232.google.com ([2a00:1450:400c:c03::232]:42503) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Y6JvK-0008Au-FR for qemu-devel@nongnu.org; Wed, 31 Dec 2014 09:06:54 -0500 Received: by mail-we0-f178.google.com with SMTP id p10so2448954wes.9 for ; Wed, 31 Dec 2014 06:06:54 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlemail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=fjy4Wt0pkUzm2OMn54I3glZIus/Ap34NwuPC7olKeUQ=; b=MsDNcLjvifMwAV5LxFwS3PG+bHj1gRIsK9uzMTi4UptE4efA/9umArlpxYesFGnTty JC7zhmaTMn21gK4lvUkoQE+5CASfvsHHuREqaiz+sRPmGCCKtlOgTDYS/ZrdEUti/ky6 yGZxGCjdGlAGLFGJmPTkYjPzbNhaLPtXRamUbAu2/4/iMSjIrFHHM9pUFV537m4/gHfS t3Of/Ds3gNvRzIL8Rdd0EDb2UsFqTGVPp3zj8DviT9SK7akkj3rlYtbC81mGjit7L5Fl D+UMl6R6egPXxoWh4/CHV3jS7nGMdd5Kra1oJD2psm0C/bfHYoJkclVxGQUmhB/UDt1R oE0g== X-Received: by 10.194.108.202 with SMTP id hm10mr133932813wjb.72.1420034814031; Wed, 31 Dec 2014 06:06:54 -0800 (PST) Received: from roadrunner.fritz.box (HSI-KBW-046-005-254-064.hsi8.kabel-badenwuerttemberg.de. [46.5.254.64]) by mx.google.com with ESMTPSA id gl5sm50329484wib.0.2014.12.31.06.06.52 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 31 Dec 2014 06:06:53 -0800 (PST) From: Colin Leitner X-Google-Original-From: Colin Leitner To: qemu-devel@nongnu.org Date: Wed, 31 Dec 2014 15:06:06 +0100 Message-Id: <1420034767-26608-2-git-send-email-colin.leitner@gmail.com> X-Mailer: git-send-email 1.7.10.4 In-Reply-To: <1420034767-26608-1-git-send-email-colin.leitner@gmail.com> References: <1419945208-13971-1-git-send-email-colin.leitner@gmail.com> <1420034767-26608-1-git-send-email-colin.leitner@gmail.com> X-detected-operating-system: by eggs.gnu.org: Error: Malformed IPv6 address (bad octet value). X-Received-From: 2a00:1450:400c:c03::232 Cc: peter.crosthwaite@xilinx.com, Colin Leitner Subject: [Qemu-devel] [PATCH v2 1/2] zynq_gpio: GPIO model for Zynq SoC X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Based on the pl061 model. This model implements all four banks with 32 I/Os each. The I/Os are placed in named groups: * bankX_in for the 32 inputs of each bank * bankX_out for the 32 outputs of each bank Basic I/O and IRQ support tested with the Zynq GPIO driver in Linux 3.12. Signed-off-by: Colin Leitner --- hw/gpio/Makefile.objs | 1 + hw/gpio/zynq-gpio.c | 386 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/gpio/zynq-gpio.h | 79 ++++++++++ 3 files changed, 466 insertions(+) create mode 100644 hw/gpio/zynq-gpio.c create mode 100644 hw/gpio/zynq-gpio.h diff --git a/hw/gpio/Makefile.objs b/hw/gpio/Makefile.objs index 1abcf17..c927c66 100644 --- a/hw/gpio/Makefile.objs +++ b/hw/gpio/Makefile.objs @@ -5,3 +5,4 @@ common-obj-$(CONFIG_ZAURUS) += zaurus.o common-obj-$(CONFIG_E500) += mpc8xxx.o obj-$(CONFIG_OMAP) += omap_gpio.o +obj-$(CONFIG_ZYNQ) += zynq-gpio.o diff --git a/hw/gpio/zynq-gpio.c b/hw/gpio/zynq-gpio.c new file mode 100644 index 0000000..2fd1712 --- /dev/null +++ b/hw/gpio/zynq-gpio.c @@ -0,0 +1,386 @@ +/* + * Zynq General Purpose IO + * + * Copyright (C) 2014 Colin Leitner + * + * Based on the PL061 model: + * Copyright (c) 2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +/* + * We model all banks as if they were fully populated. MIO pins are usually + * limited to 54 pins, but this is probably device dependent and shouldn't + * cause too much trouble. One noticeable difference is the reset value of + * INT_TYPE_1, which is 0x003fffff according to the TRM and 0xffffffff here. + * + * The output enable pins are not modeled. + */ + +#include "qemu/bitops.h" + +#include "zynq-gpio.h" + +#ifndef ZYNQ_GPIO_ERR_DEBUG +#define ZYNQ_GPIO_ERR_DEBUG 0 +#endif + +#define DB_PRINT_L(lvl, fmt, args...) do {\ + if (ZYNQ_GPIO_ERR_DEBUG >= lvl) {\ + qemu_log("zynq-gpio: %s:" fmt, __func__, ## args);\ + } \ +} while (0); + +#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args) + +static void zynq_gpio_update_out(ZynqGPIOBank *b) +{ + uint32_t changed; + uint32_t mask; + uint32_t out; + int i; + + DB_PRINT("dir = %d, data = %d\n", b->dir, b->out_data); + + /* + * We assume non-driven (DIRM = 0) outputs float high. On real hardware this + * could be different, but here we have to decide which value to set the + * output IRQ to if the direction register switches the I/O to an input. + */ + /* FIXME: This is board dependent. */ + out = (b->out_data & b->dir) | ~b->dir; + + changed = b->old_out_data ^ out; + b->old_out_data = out; + + for (i = 0; i < ZYNQ_GPIO_IOS_PER_BANK; i++) { + mask = 1 << i; + if (changed & mask) { + DB_PRINT("Set output %d = %d\n", i, (out & mask) != 0); + qemu_set_irq(b->out[i], (out & mask) != 0); + } + } +} + +static void zynq_gpio_update_in(ZynqGPIOBank *b) +{ + uint32_t changed; + uint32_t mask; + int i; + + changed = b->old_in_data ^ b->in_data; + b->old_in_data = b->in_data; + + for (i = 0; i < ZYNQ_GPIO_IOS_PER_BANK; i++) { + mask = 1 << i; + if (changed & mask) { + DB_PRINT("Changed input %d = %d\n", i, (b->in_data & mask) != 0); + + if (b->itype & mask) { + /* Edge interrupt */ + if (b->iany & mask) { + /* Any edge triggers the interrupt */ + b->istat |= mask; + } else { + /* Edge is selected by INT_POLARITY */ + b->istat |= ~(b->in_data ^ b->ipolarity) & mask; + } + } + } + } + + /* Level interrupt */ + b->istat |= ~(b->in_data ^ b->ipolarity) & ~b->itype; + + DB_PRINT("istat = %08X\n", b->istat); +} + +static void zynq_gpio_set_in_irq(ZynqGPIOState *s) +{ + int b; + uint32_t istat = 0; + + for (b = 0; b < ZYNQ_GPIO_BANKS; b++) { + istat |= s->banks[b].istat & ~s->banks[b].imask; + } + + DB_PRINT("IRQ = %d\n", istat != 0); + + qemu_set_irq(s->irq, istat != 0); +} + +static void zynq_gpio_update(ZynqGPIOState *s) +{ + int b; + + for (b = 0; b < ZYNQ_GPIO_BANKS; b++) { + zynq_gpio_update_out(&s->banks[b]); + zynq_gpio_update_in(&s->banks[b]); + } + + zynq_gpio_set_in_irq(s); +} + +static uint64_t zynq_gpio_read(void *opaque, hwaddr offset, + unsigned size) +{ + ZynqGPIOState *s = opaque; + int b; + int shift; + ZynqGPIOBank *bank; + + switch (offset) { + case ZYNQ_GPIO_REG_MASK_DATA_0_LSW...ZYNQ_GPIO_REG_MASK_DATA_3_MSW: + b = extract32(offset - ZYNQ_GPIO_REG_MASK_DATA_0_LSW, 3, 2); + shift = (offset & 0x8) ? 16 : 0; + return extract32(s->banks[b].mask_data, shift, 16); + + case ZYNQ_GPIO_REG_DATA_0...ZYNQ_GPIO_REG_DATA_3: + b = (offset - ZYNQ_GPIO_REG_DATA_0) / 4; + return s->banks[b].out_data; + + case ZYNQ_GPIO_REG_DATA_0_RO...ZYNQ_GPIO_REG_DATA_3_RO: + b = (offset - ZYNQ_GPIO_REG_DATA_0_RO) / 4; + return s->banks[b].in_data; + } + + if (offset < 0x204 || offset > 0x2e4) { + qemu_log_mask(LOG_GUEST_ERROR, + "zynq_gpio_read: Bad offset %x\n", (int)offset); + return 0; + } + + b = (offset - 0x200) / 0x40; + bank = &s->banks[b]; + + switch (offset - ZYNQ_GPIO_BANK_OFFSET(b)) { + case ZYNQ_GPIO_BANK_REG_DIRM: + return bank->dir; + case ZYNQ_GPIO_BANK_REG_OEN: + return bank->oen; + case ZYNQ_GPIO_BANK_REG_INT_MASK: + return bank->imask; + case ZYNQ_GPIO_BANK_REG_INT_EN: + return 0; + case ZYNQ_GPIO_BANK_REG_INT_DIS: + return 0; + case ZYNQ_GPIO_BANK_REG_INT_STAT: + return bank->istat; + case ZYNQ_GPIO_BANK_REG_INT_TYPE: + return bank->itype; + case ZYNQ_GPIO_BANK_REG_INT_POLARITY: + return bank->ipolarity; + case ZYNQ_GPIO_BANK_REG_INT_ANY: + return bank->iany; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "zynq_gpio_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void zynq_gpio_mask_data(ZynqGPIOBank *bank, int bit_offset, + uint32_t mask_data) +{ + DB_PRINT("mask data offset = %d, mask_data = %08X\n", bit_offset, mask_data); + + /* MASK_DATA registers are R/W on their data part */ + bank->mask_data = deposit32(bank->mask_data, bit_offset, 16, mask_data); + bank->out_data = deposit32(bank->out_data, bit_offset, 16, mask_data); + + zynq_gpio_update_out(bank); +} + +static void zynq_gpio_data(ZynqGPIOBank *bank, uint32_t data) +{ + bank->out_data = data; + zynq_gpio_update_out(bank); +} + +static void zynq_gpio_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + ZynqGPIOState *s = opaque; + int b; + int shift; + ZynqGPIOBank *bank; + + switch (offset) { + case ZYNQ_GPIO_REG_MASK_DATA_0_LSW...ZYNQ_GPIO_REG_MASK_DATA_3_MSW: + b = extract32(offset - ZYNQ_GPIO_REG_MASK_DATA_0_LSW, 3, 2); + shift = (offset & 0x8) ? 16 : 0; + zynq_gpio_mask_data(&s->banks[b], shift, value); + return; + + case ZYNQ_GPIO_REG_DATA_0...ZYNQ_GPIO_REG_DATA_3: + b = (offset - ZYNQ_GPIO_REG_DATA_0) / 4; + zynq_gpio_data(&s->banks[b], value); + return; + + case ZYNQ_GPIO_REG_DATA_0_RO...ZYNQ_GPIO_REG_DATA_3_RO: + return; + } + + if (offset < 0x204 || offset > 0x2e4) { + qemu_log_mask(LOG_GUEST_ERROR, + "zynq_gpio_write: Bad offset %x\n", (int)offset); + return; + } + + b = (offset - 0x200) / 0x40; + bank = &s->banks[b]; + + switch (offset - ZYNQ_GPIO_BANK_OFFSET(b)) { + case ZYNQ_GPIO_BANK_REG_DIRM: + bank->dir = value; + break; + case ZYNQ_GPIO_BANK_REG_OEN: + bank->oen = value; + qemu_log_mask(LOG_UNIMP, + "zynq_gpio_write: Output enable lines not implemented\n"); + break; + case ZYNQ_GPIO_BANK_REG_INT_MASK: + return; + case ZYNQ_GPIO_BANK_REG_INT_EN: + bank->imask &= ~value; + break; + case ZYNQ_GPIO_BANK_REG_INT_DIS: + bank->imask |= value; + break; + case ZYNQ_GPIO_BANK_REG_INT_STAT: + bank->istat &= ~value; + break; + case ZYNQ_GPIO_BANK_REG_INT_TYPE: + bank->itype = value; + break; + case ZYNQ_GPIO_BANK_REG_INT_POLARITY: + bank->ipolarity = value; + break; + case ZYNQ_GPIO_BANK_REG_INT_ANY: + bank->iany = value; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "zynq_gpio_write: Bad offset %x\n", (int)offset); + return; + } + + zynq_gpio_update(s); +} + +static void zynq_gpio_reset(DeviceState *d) +{ + ZynqGPIOState *s = ZYNQ_GPIO(d); + int b; + + for (b = 0; b < ZYNQ_GPIO_BANKS; b++) { + s->banks[b].mask_data = 0x00000000; + s->banks[b].out_data = 0x00000000; + s->banks[b].dir = 0x00000000; + s->banks[b].oen = 0x00000000; + s->banks[b].imask = 0x00000000; + s->banks[b].istat = 0x00000000; + s->banks[b].itype = 0xffffffff; + s->banks[b].ipolarity = 0x00000000; + s->banks[b].iany = 0x00000000; + } +} + +static void zynq_gpio_set_irq(ZynqGPIOBank *bank, int irq, int level) +{ + uint32_t mask = 1 << irq; + + bank->in_data &= ~mask; + if (level) + bank->in_data |= mask; + + zynq_gpio_update_in(bank); + + zynq_gpio_set_in_irq(bank->parent); +} + +static void zynq_gpio_set_bank0_irq(void *opaque, int irq, int level) +{ + ZynqGPIOState *s = opaque; + zynq_gpio_set_irq(&s->banks[0], irq, level); +} + +static void zynq_gpio_set_bank1_irq(void *opaque, int irq, int level) +{ + ZynqGPIOState *s = opaque; + zynq_gpio_set_irq(&s->banks[1], irq, level); +} + +static void zynq_gpio_set_bank2_irq(void *opaque, int irq, int level) +{ + ZynqGPIOState *s = opaque; + zynq_gpio_set_irq(&s->banks[2], irq, level); +} + +static void zynq_gpio_set_bank3_irq(void *opaque, int irq, int level) +{ + ZynqGPIOState *s = opaque; + zynq_gpio_set_irq(&s->banks[3], irq, level); +} + +static const MemoryRegionOps zynq_gpio_ops = { + .read = zynq_gpio_read, + .write = zynq_gpio_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void zynq_gpio_realize(DeviceState *dev, Error **errp) +{ + ZynqGPIOState *s = ZYNQ_GPIO(dev); + int b; + + memory_region_init_io(&s->iomem, OBJECT(s), &zynq_gpio_ops, s, "zynq-gpio", 0x1000); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); + + for (b = 0; b < ZYNQ_GPIO_BANKS; b++) { + char name[16]; + + s->banks[b].parent = s; + + snprintf(name, sizeof(name), "bank%d_out", b); + qdev_init_gpio_out_named(dev, s->banks[b].out, name, ZYNQ_GPIO_IOS_PER_BANK); + /* + * TODO: it would be nice if we could pass the bank to the handler. This + * would allow us to remove the 4 callbacks and use zynq_gpio_set_irq + * directly. + */ +#if 0 + snprintf(name, sizeof(name), "bank%d_in", b); + qdev_init_gpio_in_named(dev, zynq_gpio_set_irq, name, ZYNQ_GPIO_IOS_PER_BANK, &s->banks[b]); +#endif + } + qdev_init_gpio_in_named(dev, zynq_gpio_set_bank0_irq, "bank0_in", ZYNQ_GPIO_IOS_PER_BANK); + qdev_init_gpio_in_named(dev, zynq_gpio_set_bank1_irq, "bank1_in", ZYNQ_GPIO_IOS_PER_BANK); + qdev_init_gpio_in_named(dev, zynq_gpio_set_bank2_irq, "bank2_in", ZYNQ_GPIO_IOS_PER_BANK); + qdev_init_gpio_in_named(dev, zynq_gpio_set_bank3_irq, "bank3_in", ZYNQ_GPIO_IOS_PER_BANK); +} + +static void zynq_gpio_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = zynq_gpio_realize; + dc->reset = zynq_gpio_reset; +} + +static const TypeInfo zynq_gpio_info = { + .name = TYPE_ZYNQ_GPIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(ZynqGPIOState), + .class_init = zynq_gpio_class_init, +}; + +static void zynq_gpio_register_type(void) +{ + type_register_static(&zynq_gpio_info); +} + +type_init(zynq_gpio_register_type) diff --git a/hw/gpio/zynq-gpio.h b/hw/gpio/zynq-gpio.h new file mode 100644 index 0000000..8146c1f --- /dev/null +++ b/hw/gpio/zynq-gpio.h @@ -0,0 +1,79 @@ +/* + * Zynq General Purpose IO + * + * Copyright (C) 2014 Colin Leitner + * + * This code is licensed under the GPL. + */ + +#ifndef HW_ZYNQ_GPIO_H +#define HW_ZYNQ_GPIO_H + +#include "hw/sysbus.h" + +#define TYPE_ZYNQ_GPIO "zynq-gpio" +#define ZYNQ_GPIO(obj) OBJECT_CHECK(ZynqGPIOState, (obj), TYPE_ZYNQ_GPIO) + +#define ZYNQ_GPIO_BANKS 4 +#define ZYNQ_GPIO_IOS_PER_BANK 32 + +typedef struct { + struct ZynqGPIOState *parent; + + uint32_t mask_data; + uint32_t out_data; + uint32_t old_out_data; + uint32_t in_data; + uint32_t old_in_data; + uint32_t dir; + uint32_t oen; + uint32_t imask; + uint32_t istat; + uint32_t itype; + uint32_t ipolarity; + uint32_t iany; + + qemu_irq out[ZYNQ_GPIO_IOS_PER_BANK]; +} ZynqGPIOBank; + +typedef struct ZynqGPIOState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + ZynqGPIOBank banks[ZYNQ_GPIO_BANKS]; + qemu_irq irq; +} ZynqGPIOState; + +#define ZYNQ_GPIO_REG_MASK_DATA_0_LSW 0x000 +#define ZYNQ_GPIO_REG_MASK_DATA_0_MSW 0x004 +#define ZYNQ_GPIO_REG_MASK_DATA_1_LSW 0x008 +#define ZYNQ_GPIO_REG_MASK_DATA_1_MSW 0x00C +#define ZYNQ_GPIO_REG_MASK_DATA_2_LSW 0x010 +#define ZYNQ_GPIO_REG_MASK_DATA_2_MSW 0x014 +#define ZYNQ_GPIO_REG_MASK_DATA_3_LSW 0x018 +#define ZYNQ_GPIO_REG_MASK_DATA_3_MSW 0x01C +#define ZYNQ_GPIO_REG_DATA_0 0x040 +#define ZYNQ_GPIO_REG_DATA_1 0x044 +#define ZYNQ_GPIO_REG_DATA_2 0x048 +#define ZYNQ_GPIO_REG_DATA_3 0x04C +#define ZYNQ_GPIO_REG_DATA_0_RO 0x060 +#define ZYNQ_GPIO_REG_DATA_1_RO 0x064 +#define ZYNQ_GPIO_REG_DATA_2_RO 0x068 +#define ZYNQ_GPIO_REG_DATA_3_RO 0x06C + +/* + * Oddly enough these registers are neatly grouped per bank and not interleaved + * like the data registers + */ +#define ZYNQ_GPIO_BANK_OFFSET(bank) (0x200 + 0x40 * (bank)) +#define ZYNQ_GPIO_BANK_REG_DIRM 0x04 +#define ZYNQ_GPIO_BANK_REG_OEN 0x08 +#define ZYNQ_GPIO_BANK_REG_INT_MASK 0x0C +#define ZYNQ_GPIO_BANK_REG_INT_EN 0x10 +#define ZYNQ_GPIO_BANK_REG_INT_DIS 0x14 +#define ZYNQ_GPIO_BANK_REG_INT_STAT 0x18 +#define ZYNQ_GPIO_BANK_REG_INT_TYPE 0x1C +#define ZYNQ_GPIO_BANK_REG_INT_POLARITY 0x20 +#define ZYNQ_GPIO_BANK_REG_INT_ANY 0x24 + +#endif