qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] gpio: designware gpio driver


From: Ben Dooks
Subject: [PATCH] gpio: designware gpio driver
Date: Wed, 13 Jul 2022 18:20:10 +0100

A model for the DesignWare GPIO (v1) block.

Signed-off-by: Ben Dooks <ben.dooks@sifive.com>
---
 hw/gpio/Kconfig                   |   3 +
 hw/gpio/designware_gpio.c         | 327 ++++++++++++++++++++++++++++++
 hw/gpio/meson.build               |   1 +
 hw/gpio/trace-events              |   7 +
 include/hw/gpio/designware_gpio.h |  89 ++++++++
 5 files changed, 427 insertions(+)
 create mode 100644 hw/gpio/designware_gpio.c
 create mode 100644 include/hw/gpio/designware_gpio.h

diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig
index f0e7405f6e..e5883d9763 100644
--- a/hw/gpio/Kconfig
+++ b/hw/gpio/Kconfig
@@ -13,3 +13,6 @@ config GPIO_PWR
 
 config SIFIVE_GPIO
     bool
+
+config DESIGNWARE_GPIO
+    bool
\ No newline at end of file
diff --git a/hw/gpio/designware_gpio.c b/hw/gpio/designware_gpio.c
new file mode 100644
index 0000000000..417bccd630
--- /dev/null
+++ b/hw/gpio/designware_gpio.c
@@ -0,0 +1,327 @@
+/*
+ * Synopsys Desgignware general purpose input/output register definition
+ *
+ * Based on sifive_gpio.c and imx_gpio.c
+ *
+ * Copyright 2022 Sifive, Inc.
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/gpio/designware_gpio.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+/* only bank A can provide interrupts */
+static void update_output_irqs(DESIGNWAREGPIOState *s)
+{
+    struct DESIGNWAREGPIOBank *bank = &s->bank[0];
+    uint32_t level_irqs, edge_irqs = 0;
+
+    /* re-calculate interrupts for raw_int_status */
+    level_irqs = bank->dr_val ^ s->int_polarity;
+    level_irqs &= ~s->int_level;
+
+    edge_irqs = bank->dr_val ^ bank->last_dr_val;
+    edge_irqs &= s->int_level;
+    bank->last_dr_val = bank->dr_val;
+
+    /* update irq from raw-status and the mask */
+    s->int_status_raw = level_irqs | edge_irqs;
+    s->int_status = s->int_status_raw & s->int_mask;
+
+    qemu_set_irq(s->irq, s->int_status ? 1 : 0);
+    trace_designware_gpio_update_output_irq(s->int_status);
+}
+
+static void update_state(DESIGNWAREGPIOState *s)
+{
+    struct DESIGNWAREGPIOBank *bank;
+    int banknr, basenr, nr;
+
+    for (banknr = 0; banknr < DESIGNWARE_GPIO_BANKS; banknr++) {
+        basenr = banknr * DESIGNWARE_GPIO_NR_PER_BANK;
+        bank = &s->bank[banknr];
+
+        /* check for data-direction differences */
+        if (bank->ddr & bank->in) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "GPIO bank %d: pins shorted, DDR=%x, IN=%x, 
overlap=%x\n",
+                          banknr, bank->ddr, bank->in, bank->ddr & bank->in);
+        }
+
+        bank->dr_val = (bank->dr & bank->ddr) | (bank->in & ~bank->ddr);
+
+        /* update any outputs marked as outputs */
+        for (nr = 0; nr < DESIGNWARE_GPIO_NR_PER_BANK; nr++) {
+            if (!extract32(bank->ddr, nr, 1))
+                continue;
+            qemu_set_irq(s->output[basenr+nr], extract32(bank->dr_val, nr, 1));
+        }
+    }
+
+    update_output_irqs(s);
+}
+
+
+static uint64_t designware_gpio_read(void *opaque, hwaddr offset, unsigned int 
size)
+{
+    struct DESIGNWAREGPIOState *s = DESIGNWARE_GPIO(opaque);
+    struct DESIGNWAREGPIOBank *bank;
+    hwaddr banknr, reg;
+    uint64_t r = 0;
+    bool handled = true;
+
+    if (offset < (REG_SWPORTD_DDR + 4)) {
+        banknr = offset / REG_SWPORT_DR_STRIDE;
+        reg = offset % REG_SWPORT_DR_STRIDE;
+        bank = &s->bank[banknr];
+
+        switch (reg) {
+        case REG_SWPORTA_DR:
+            r = bank->dr;
+            break;
+        case REG_SWPORTA_DDR:
+            r = bank->ddr;
+            break;
+        default:
+            handled = false;
+        }
+    } else {
+        switch (offset) {
+        case REG_INTEN:
+            r= s->int_en;
+            break;
+        case REG_INTMASK:
+            r = s->int_mask;
+            break;
+        case REG_INTTYPE_LEVEL:
+            r = s->int_level;
+            break;
+        case REG_INT_POLARITY:
+            r = s->int_polarity;
+            break;
+        case REG_INTSTATUS:
+            r = s->int_status;
+            break;
+        case REG_INTSTATUS_RAW:
+            r = s->int_status_raw;
+            break;
+        case REG_PORTA_DEBOUNCE:
+            r = s->porta_debounce;
+            break;
+        case REG_PORTA_EOI:
+            r = 0x0;    /* write only */
+            break;
+        case REG_EXT_PORTA:
+            r = s->bank[0].dr_val;
+            break;
+        case REG_EXT_PORTB:
+            r = s->bank[1].dr_val;
+            break;
+        case REG_EXT_PORTC:
+            r = s->bank[2].dr_val;
+            break;
+        case REG_EXT_PORTD:
+            r = s->bank[3].dr_val;
+            break;
+        case REG_ID:
+            r = 0x0;
+            break;
+        default:
+            handled = false;
+        }
+    }
+
+    if (!handled)
+        qemu_log_mask(LOG_GUEST_ERROR,
+                "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+
+    trace_designware_gpio_read(offset, r);
+
+    return r;
+}
+
+static void designware_gpio_write(void *opaque, hwaddr offset,
+                              uint64_t value, unsigned int size)
+{
+    struct DESIGNWAREGPIOState *s = DESIGNWARE_GPIO(opaque);
+    struct DESIGNWAREGPIOBank *bank;
+    hwaddr banknr, reg;
+    bool handled = true;
+
+    trace_designware_gpio_write(offset, value);
+
+    if (offset < (REG_SWPORTD_DDR + 4)) {
+        banknr = offset / REG_SWPORT_DR_STRIDE;
+        reg = offset % REG_SWPORT_DR_STRIDE;
+        bank = &s->bank[banknr];
+
+        switch (reg) {
+        case REG_SWPORTA_DR:
+            bank->dr = value;
+            break;
+        case REG_SWPORTA_DDR:
+            bank->ddr = value;
+            break;
+        default:
+            handled = false;
+        }
+    } else {
+        switch (offset) {
+        case REG_INTEN:
+            s->int_en = value;
+            break;
+        case REG_INTMASK:
+            s->int_mask = value;
+            break;
+        case REG_INTTYPE_LEVEL:
+            s->int_level = value;
+            break;
+        case REG_INT_POLARITY:
+            s->int_polarity = value;
+            break;
+        case REG_INTSTATUS:
+            /* read only */
+        case REG_INTSTATUS_RAW:
+            /* read only */
+            break;
+        case REG_PORTA_DEBOUNCE:
+            s->porta_debounce = value;
+            break;
+        case REG_PORTA_EOI:
+            /* assume level irqs will just re-trigger */
+            s->int_status_raw &= ~value;
+            break;
+        case REG_EXT_PORTA:
+        case REG_EXT_PORTB:
+        case REG_EXT_PORTC:
+        case REG_EXT_PORTD:
+            /* read only, ignore */
+            break;
+        }
+    }
+
+    if (!handled)
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+
+    update_state(s);
+}
+
+
+static const MemoryRegionOps gpio_ops = {
+    .read =  designware_gpio_read,
+    .write = designware_gpio_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl.min_access_size = 4,
+    .impl.max_access_size = 4,
+};
+
+static void designware_gpio_set(void *opaque, int line, int value)
+{
+    DESIGNWAREGPIOState *s = DESIGNWARE_GPIO(opaque);
+    struct DESIGNWAREGPIOBank *bank = &s->bank[line / 
DESIGNWARE_GPIO_NR_PER_BANK];
+
+    trace_designware_gpio_set(line, value);
+    assert(line >= 0 && line < DESIGNWARE_GPIO_PINS);
+
+    bank->in_mask = deposit32(bank->in_mask, line, 1, value >= 0);
+    if (value >= 0) {
+        bank->in = deposit32(bank->in, line, 1, value != 0);
+    }
+
+    update_state(s);
+}
+
+static void designware_gpio_reset(DeviceState *dev)
+{
+    DESIGNWAREGPIOState *s = DESIGNWARE_GPIO(dev);
+
+    memset(s->bank, 0, sizeof(s->bank));
+    s->int_en = 0;
+    s->int_mask = 0;
+    s->int_level = 0;
+    s->int_polarity = 0;
+    s->int_status = 0;
+    s->porta_debounce = 0;
+}
+
+#define STATE_BANK(__nr) \
+    VMSTATE_UINT32(bank[__nr].dr,      DESIGNWAREGPIOState), \
+    VMSTATE_UINT32(bank[__nr].dr_val,  DESIGNWAREGPIOState), \
+    VMSTATE_UINT32(bank[__nr].ddr,     DESIGNWAREGPIOState), \
+    VMSTATE_UINT32(bank[__nr].in,      DESIGNWAREGPIOState), \
+    VMSTATE_UINT32(bank[__nr].in_mask,  DESIGNWAREGPIOState)
+
+static const VMStateDescription vmstate_designware_gpio = {
+    .name = TYPE_DESIGNWARE_GPIO,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        STATE_BANK(0),
+        STATE_BANK(1),
+        STATE_BANK(2),
+        STATE_BANK(3),
+        VMSTATE_UINT32(int_en,       DESIGNWAREGPIOState),
+        VMSTATE_UINT32(int_mask,     DESIGNWAREGPIOState),
+        VMSTATE_UINT32(int_level,    DESIGNWAREGPIOState),
+        VMSTATE_UINT32(int_polarity, DESIGNWAREGPIOState),
+        VMSTATE_UINT32(int_status,   DESIGNWAREGPIOState),
+        VMSTATE_UINT32(int_status_raw, DESIGNWAREGPIOState),
+        VMSTATE_UINT32(porta_debounce, DESIGNWAREGPIOState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static Property designware_gpio_properties[] = {
+    DEFINE_PROP_UINT32("ngpio", DESIGNWAREGPIOState, ngpio, 
DESIGNWARE_GPIO_PINS),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void designware_gpio_realize(DeviceState *dev, Error **errp)
+{
+    DESIGNWAREGPIOState *s = DESIGNWARE_GPIO(dev);
+
+    memory_region_init_io(&s->mmio, OBJECT(dev), &gpio_ops, s,
+            TYPE_DESIGNWARE_GPIO, DESIGNWARE_GPIO_SIZE);
+
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
+    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+
+    qdev_init_gpio_in(DEVICE(s), designware_gpio_set, s->ngpio);
+    qdev_init_gpio_out(DEVICE(s), s->output, s->ngpio);
+}
+
+static void designware_gpio_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    device_class_set_props(dc, designware_gpio_properties);
+    dc->vmsd = &vmstate_designware_gpio;
+    dc->realize = designware_gpio_realize;
+    dc->reset = designware_gpio_reset;
+    dc->desc = "SiFive GPIO";
+}
+
+static const TypeInfo designware_gpio_info = {
+    .name = TYPE_DESIGNWARE_GPIO,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(DESIGNWAREGPIOState),
+    .class_init = designware_gpio_class_init
+};
+
+static void designware_gpio_register_types(void)
+{
+    type_register_static(&designware_gpio_info);
+}
+
+type_init(designware_gpio_register_types)
diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build
index 7bd6a57264..66959c33de 100644
--- a/hw/gpio/meson.build
+++ b/hw/gpio/meson.build
@@ -5,6 +5,7 @@ softmmu_ss.add(when: 'CONFIG_MAX7310', if_true: 
files('max7310.c'))
 softmmu_ss.add(when: 'CONFIG_PL061', if_true: files('pl061.c'))
 softmmu_ss.add(when: 'CONFIG_ZAURUS', if_true: files('zaurus.c'))
 
+softmmu_ss.add(when: 'CONFIG_DESIGNWARE_GPIO', if_true: 
files('designware_gpio.c'))
 softmmu_ss.add(when: 'CONFIG_IMX', if_true: files('imx_gpio.c'))
 softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_gpio.c'))
 softmmu_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_gpio.c'))
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
index 9736b362ac..45dac8b706 100644
--- a/hw/gpio/trace-events
+++ b/hw/gpio/trace-events
@@ -31,3 +31,10 @@ sifive_gpio_update_output_irq(int64_t line, int64_t value) 
"line %" PRIi64 " val
 # aspeed_gpio.c
 aspeed_gpio_read(uint64_t offset, uint64_t value) "offset: 0x%" PRIx64 " value 
0x%" PRIx64
 aspeed_gpio_write(uint64_t offset, uint64_t value) "offset: 0x%" PRIx64 " 
value 0x%" PRIx64
+
+# designware_gpio
+designware_gpio_read(uint64_t offset, uint64_t r) "offset 0x%" PRIx64 " value 
0x%" PRIx64
+designware_gpio_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " 
value 0x%" PRIx64
+designware_gpio_set(int64_t line, int64_t value) "line %" PRIi64 " value %" 
PRIi64
+designware_gpio_update_output_irq(int64_t value) " value %" PRIi64
+
diff --git a/include/hw/gpio/designware_gpio.h 
b/include/hw/gpio/designware_gpio.h
new file mode 100644
index 0000000000..2d151d3823
--- /dev/null
+++ b/include/hw/gpio/designware_gpio.h
@@ -0,0 +1,89 @@
+/*
+ * Designware System-on-Chip general purpose input/output register definition
+ *
+ * Copyright 2022 SiFive, Inc.
+ * Copyright (c) 2011 Jamie Iles
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+*/
+
+#ifndef DESIGNWARE_GPIO_H
+#define DESIGNWARE_GPIO_H
+
+#include "hw/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_DESIGNWARE_GPIO "designware.gpio"
+typedef struct DESIGNWAREGPIOState DESIGNWAREGPIOState;
+DECLARE_INSTANCE_CHECKER(DESIGNWAREGPIOState, DESIGNWARE_GPIO,
+                         TYPE_DESIGNWARE_GPIO)
+
+/* maximum pins for 4 banks */
+#define DESIGNWARE_GPIO_BANKS (4)
+#define DESIGNWARE_GPIO_NR_PER_BANK (32)
+#define DESIGNWARE_GPIO_PINS (32 * DESIGNWARE_GPIO_BANKS)
+
+#define DESIGNWARE_GPIO_SIZE 0x100
+
+/* registers copied from linux driver */
+#define REG_SWPORTA_DR         0x00
+#define REG_SWPORTA_DDR         0x04
+#define REG_SWPORTB_DR         0x0c
+#define REG_SWPORTB_DDR         0x10
+#define REG_SWPORTC_DR         0x18
+#define REG_SWPORTC_DDR         0x1c
+#define REG_SWPORTD_DR         0x24
+#define REG_SWPORTD_DDR         0x28
+#define REG_INTEN              0x30
+#define REG_INTMASK            0x34
+#define REG_INTTYPE_LEVEL      0x38
+#define REG_INT_POLARITY       0x3c
+#define REG_INTSTATUS          0x40
+#define REG_INTSTATUS_RAW       0x44
+#define REG_PORTA_DEBOUNCE     0x48
+#define REG_PORTA_EOI          0x4c    /* write to clear edge irq */
+#define REG_EXT_PORTA          0x50
+#define REG_EXT_PORTB          0x54
+#define REG_EXT_PORTC          0x58
+#define REG_EXT_PORTD          0x5c
+#define REG_ID                  0x64
+
+#define REG_EXT_PORT_STRIDE    0x04 /* register stride 32 bits */
+#define REG_SWPORT_DR_STRIDE   0x0c /* register stride 3*32 bits */
+#define REG_SWPORT_DDR_STRIDE  0x0c /* register stride 3*32 bits */
+\
+    
+struct DESIGNWAREGPIOBank {
+    uint32_t    dr;
+    uint32_t    dr_val;
+    uint32_t    ddr;    /* 0=in, 1=out */
+    uint32_t    last_dr_val;
+
+    /* internal state */
+    /* state from user */
+    uint32_t    in_mask;
+    uint32_t    in;
+};
+
+struct DESIGNWAREGPIOState {
+    SysBusDevice parent_obj;
+    MemoryRegion mmio;
+
+    qemu_irq irq;
+    qemu_irq output[DESIGNWARE_GPIO_PINS];
+
+    struct DESIGNWAREGPIOBank bank[DESIGNWARE_GPIO_BANKS];
+
+    uint32_t int_en;
+    uint32_t int_mask;
+    uint32_t int_level;         /* 0 = level, 1 = edge */
+    uint32_t int_polarity;
+    uint32_t int_status;
+    uint32_t int_status_raw;
+    uint32_t porta_debounce;
+    /* config */
+    uint32_t ngpio;
+};
+
+#endif /* DESIGNWARE_GPIO_H */
-- 
2.35.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]