From patchwork Tue Dec 30 13:13:27 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Colin Leitner X-Patchwork-Id: 424633 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 3D42514007F for ; Wed, 31 Dec 2014 00:14:52 +1100 (AEDT) Received: from localhost ([::1]:36914 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Y5wdO-0003L4-D2 for incoming@patchwork.ozlabs.org; Tue, 30 Dec 2014 08:14:50 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:33014) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Y5wcp-0002Oo-RR for qemu-devel@nongnu.org; Tue, 30 Dec 2014 08:14:17 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Y5wck-00032J-9x for qemu-devel@nongnu.org; Tue, 30 Dec 2014 08:14:15 -0500 Received: from mail-wi0-x230.google.com ([2a00:1450:400c:c05::230]:56011) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Y5wcj-00032F-VU for qemu-devel@nongnu.org; Tue, 30 Dec 2014 08:14:10 -0500 Received: by mail-wi0-f176.google.com with SMTP id ex7so24044761wid.3 for ; Tue, 30 Dec 2014 05:14:09 -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=NOt/ZT88zmNYj8wIRI+r5qaSqti101+vTKmQVFcRaR8=; b=fZqicqYfQmivkv5vMeHDkm6yyMdQD49+w1xzsEFDZM5b7XEaLSHj4LMVrRGYWxNSmo q8a9Eb3N6zcwRzrwoLlWC94d9qp8LCby/VWZWZgKRBft6A8qL98yAZy0VbFBuxGRqany YfNCRd/65Kk9IvvFfKlMt8VwOmwLHNA1Hci0LFgK14UHdhd4tZZHutuw34CZ3mDNqb6g Q9jSq51SV63Vb8VqjZU0PEw1L3E9QAiKIrFIhlgQN6lQ5cbIR51Emad/mwDbEG3gUYzy F2s8Y/o0PSByxH/qZym/8Tyd2Y6lKqMlEV67jbZcfYan31Jqxx5SaR/6C4HYYKb3tzcT QA4w== X-Received: by 10.180.205.163 with SMTP id lh3mr107631243wic.63.1419945249245; Tue, 30 Dec 2014 05:14:09 -0800 (PST) Received: from roadrunner.fritz.box (HSI-KBW-046-005-254-094.hsi8.kabel-badenwuerttemberg.de. [46.5.254.94]) by mx.google.com with ESMTPSA id gl5sm46513366wib.0.2014.12.30.05.14.07 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 30 Dec 2014 05:14:08 -0800 (PST) From: Colin Leitner X-Google-Original-From: Colin Leitner To: qemu-devel@nongnu.org Date: Tue, 30 Dec 2014 14:13:27 +0100 Message-Id: <1419945208-13971-2-git-send-email-colin.leitner@gmail.com> X-Mailer: git-send-email 1.7.10.4 In-Reply-To: <1419945208-13971-1-git-send-email-colin.leitner@gmail.com> References: <1419945208-13971-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:c05::230 Cc: peter.crosthwaite@xilinx.com, Colin Leitner Subject: [Qemu-devel] [PATCH 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 four named groups: * mio_in/out[0..63], where mio_in/out[0..31] map to bank 0 and the rest to bank 1 * emio_in/out[0..63], where emio_in/out[0..31] map to bank 2 and the rest to bank 3 Basic I/O 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 | 441 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 442 insertions(+) create mode 100644 hw/gpio/zynq_gpio.c diff --git a/hw/gpio/Makefile.objs b/hw/gpio/Makefile.objs index 1abcf17..32b99e0 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..2119561 --- /dev/null +++ b/hw/gpio/zynq_gpio.c @@ -0,0 +1,441 @@ +/* + * 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 noticable 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 "hw/sysbus.h" + +//#define DEBUG_ZYNQ_GPIO 1 + +#ifdef DEBUG_ZYNQ_GPIO +#define DPRINTF(fmt, ...) \ +do { printf("zynq-gpio: " fmt , ## __VA_ARGS__); } while (0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "zynq-gpio: error: " fmt , ## __VA_ARGS__); exit(1);} while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "zynq-gpio: error: " fmt , ## __VA_ARGS__);} while (0) +#endif + +#define TYPE_ZYNQ_GPIO "zynq-gpio" +#define ZYNQ_GPIO(obj) OBJECT_CHECK(ZynqGPIOState, (obj), TYPE_ZYNQ_GPIO) + +typedef struct { + 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; +} GPIOBank; + +typedef struct ZynqGPIOState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + GPIOBank banks[4]; + qemu_irq mio_out[64]; + qemu_irq emio_out[64]; + qemu_irq irq; +} ZynqGPIOState; + +static void zynq_gpio_update_out(GPIOBank *b) +{ + uint32_t changed; + uint32_t mask; + uint32_t out; + int i; + + DPRINTF("dir = %d, data = %d\n", b->dir, b->out_data); + + /* Outputs float high. */ + /* FIXME: This is board dependent. */ + out = (b->out_data & b->dir) | ~b->dir; + changed = b->old_out_data ^ out; + if (changed) { + b->old_out_data = out; + for (i = 0; i < 32; i++) { + mask = 1 << i; + if (changed & mask) { + DPRINTF("Set output %d = %d\n", i, (out & mask) != 0); + qemu_set_irq(b->out[i], (out & mask) != 0); + } + } + } +} + +static void zynq_gpio_update_in(GPIOBank *b) +{ + uint32_t changed; + uint32_t mask; + int i; + + changed = b->old_in_data ^ b->in_data; + if (changed) { + b->old_in_data = b->in_data; + for (i = 0; i < 32; i++) { + mask = 1 << i; + if (changed & mask) { + DPRINTF("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; + + DPRINTF("istat = %08X\n", b->istat); +} + +static void zynq_gpio_set_in_irq(ZynqGPIOState *s) +{ + int b; + uint32_t istat = 0; + + for (b = 0; b < 4; b++) { + istat |= s->banks[b].istat & ~s->banks[b].imask; + } + + DPRINTF("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 < 4; 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 = (ZynqGPIOState *)opaque; + int b; + GPIOBank *bank; + + switch (offset) { + case 0x000: /* MASK_DATA_0_LSW */ + return (s->banks[0].mask_data >> 0) & 0xffff; + case 0x004: /* MASK_DATA_0_MSW */ + return (s->banks[0].mask_data >> 16) & 0xffff; + case 0x008: /* MASK_DATA_1_LSW */ + return (s->banks[1].mask_data >> 0) & 0xffff; + case 0x00C: /* MASK_DATA_1_MSW */ + return (s->banks[1].mask_data >> 16) & 0xffff; + case 0x010: /* MASK_DATA_2_LSW */ + return (s->banks[2].mask_data >> 0) & 0xffff; + case 0x014: /* MASK_DATA_2_MSW */ + return (s->banks[2].mask_data >> 16) & 0xffff; + case 0x018: /* MASK_DATA_3_LSW */ + return (s->banks[3].mask_data >> 0) & 0xffff; + case 0x01C: /* MASK_DATA_3_MSW */ + return (s->banks[3].mask_data >> 16) & 0xffff; + + case 0x040: /* DATA_0 */ + return s->banks[0].out_data; + case 0x044: /* DATA_1 */ + return s->banks[1].out_data; + case 0x048: /* DATA_2 */ + return s->banks[2].out_data; + case 0x04C: /* DATA_3 */ + return s->banks[3].out_data; + + case 0x060: /* DATA_0_RO */ + return s->banks[0].in_data; + case 0x064: /* DATA_1_RO */ + return s->banks[1].in_data; + case 0x068: /* DATA_2_RO */ + return s->banks[2].in_data; + case 0x06C: /* DATA_3_RO */ + return s->banks[3].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 - 0x200 - b * 0x40) { + case 0x04: /* DIRM_x */ + return bank->dir; + case 0x08: /* OEN_x */ + return bank->oen; + case 0x0C: /* INT_MASK_x */ + return bank->imask; + case 0x10: /* INT_EN_x */ + return 0; + case 0x14: /* INT_DIS_x */ + return 0; + case 0x18: /* INT_STAT_x */ + return bank->istat; + case 0x1C: /* INT_TYPE_x */ + return bank->itype; + case 0x20: /* INT_POLARITY_x */ + return bank->ipolarity; + case 0x24: /* INT_ANY_x */ + 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(GPIOBank *bank, int bit_offset, + uint32_t mask_data) +{ + DPRINTF("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 = (bank->mask_data & ~(0xffff << bit_offset)) | + ((mask_data & 0xffff) << bit_offset); + bank->out_data = (bank->out_data & ~((~mask_data >> 16) << bit_offset)) | + ((mask_data & 0xffff) << bit_offset); + zynq_gpio_update_out(bank); +} + +static void zynq_gpio_data(GPIOBank *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 = (ZynqGPIOState *)opaque; + int b; + GPIOBank *bank; + + switch (offset) { + case 0x000: /* MASK_DATA_0_LSW */ + zynq_gpio_mask_data(&s->banks[0], 0, value); + return; + case 0x004: /* MASK_DATA_0_MSW */ + zynq_gpio_mask_data(&s->banks[0], 16, value); + return; + case 0x008: /* MASK_DATA_1_LSW */ + zynq_gpio_mask_data(&s->banks[1], 0, value); + return; + case 0x00C: /* MASK_DATA_1_MSW */ + zynq_gpio_mask_data(&s->banks[1], 16, value); + return; + case 0x010: /* MASK_DATA_2_LSW */ + zynq_gpio_mask_data(&s->banks[2], 0, value); + return; + case 0x014: /* MASK_DATA_2_MSW */ + zynq_gpio_mask_data(&s->banks[2], 16, value); + return; + case 0x018: /* MASK_DATA_3_LSW */ + zynq_gpio_mask_data(&s->banks[3], 0, value); + return; + case 0x01C: /* MASK_DATA_3_MSW */ + zynq_gpio_mask_data(&s->banks[3], 16, value); + return; + + case 0x040: /* DATA_0 */ + zynq_gpio_data(&s->banks[0], value); + return; + case 0x044: /* DATA_1 */ + zynq_gpio_data(&s->banks[1], value); + return; + case 0x048: /* DATA_2 */ + zynq_gpio_data(&s->banks[2], value); + return; + case 0x04C: /* DATA_3 */ + zynq_gpio_data(&s->banks[3], value); + return; + + case 0x060: /* DATA_0_RO */ + case 0x064: /* DATA_1_RO */ + case 0x068: /* DATA_2_RO */ + case 0x06C: /* 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 - 0x200 - b * 0x40) { + case 0x04: /* DIRM_x */ + bank->dir = value; + break; + case 0x08: /* OEN_x */ + bank->oen = value; + break; + case 0x0C: /* INT_MASK_x */ + return; + case 0x10: /* INT_EN_x */ + bank->imask &= ~value; + break; + case 0x14: /* INT_DIS_x */ + bank->imask |= value; + break; + case 0x18: /* INT_STAT_x */ + bank->istat &= ~value; + break; + case 0x1C: /* INT_TYPE_x */ + bank->itype = value; + break; + case 0x20: /* INT_POLARITY_x */ + bank->ipolarity = value; + break; + case 0x24: /* INT_ANY_x */ + 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(ZynqGPIOState *s) +{ + int b; + + for (b = 0; b < 4; 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(void * opaque, int irq, int level) +{ + ZynqGPIOState *s = (ZynqGPIOState *)opaque; + + GPIOBank *bank = &s->banks[irq / 32]; + uint32_t mask = 1 << (irq % 32); + + bank->in_data &= ~mask; + if (level) + bank->in_data |= mask; + + zynq_gpio_update_in(bank); + + zynq_gpio_set_in_irq(s); +} + +static void zynq_gpio_set_mio_irq(void * opaque, int irq, int level) +{ + zynq_gpio_set_irq(opaque, irq + 0, level); +} + +static void zynq_gpio_set_emio_irq(void * opaque, int irq, int level) +{ + zynq_gpio_set_irq(opaque, irq + 64, level); +} + +static const MemoryRegionOps zynq_gpio_ops = { + .read = zynq_gpio_read, + .write = zynq_gpio_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int zynq_gpio_initfn(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + ZynqGPIOState *s = ZYNQ_GPIO(dev); + + s->banks[0].out = &s->mio_out[0]; + s->banks[1].out = &s->mio_out[32]; + s->banks[2].out = &s->emio_out[0]; + s->banks[3].out = &s->emio_out[32]; + + memory_region_init_io(&s->iomem, OBJECT(s), &zynq_gpio_ops, s, "zynq_gpio", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + + qdev_init_gpio_in_named(dev, zynq_gpio_set_mio_irq, "mio_in", 64); + qdev_init_gpio_in_named(dev, zynq_gpio_set_emio_irq, "emio_in", 64); + + qdev_init_gpio_out_named(dev, s->mio_out, "mio_out", 64); + qdev_init_gpio_out_named(dev, s->emio_out, "emio_out", 64); + + zynq_gpio_reset(s); + + return 0; +} + +static void zynq_gpio_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = zynq_gpio_initfn; +} + +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)