diff mbox series

gpio: designware gpio driver

Message ID 20220713172010.39163-1-ben.dooks@sifive.com (mailing list archive)
State New, archived
Headers show
Series gpio: designware gpio driver | expand

Commit Message

Ben Dooks July 13, 2022, 5:20 p.m. UTC
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

Comments

Ben Dooks July 18, 2022, 10:05 a.m. UTC | #1
On 13/07/2022 18:20, Ben Dooks wrote:
> A model for the DesignWare GPIO (v1) block.

Is there anyone else who should be reviewing these that
was missed off the original list? I'd like to get an idea
if there is any work to do. I've got a couple more drivers
to submit and was waiting on feedback from this before
getting these submitted.
Peter Maydell July 18, 2022, 10:15 a.m. UTC | #2
On Mon, 18 Jul 2022 at 11:05, Ben Dooks <ben.dooks@sifive.com> wrote:
>
> On 13/07/2022 18:20, Ben Dooks wrote:
> > A model for the DesignWare GPIO (v1) block.
>
> Is there anyone else who should be reviewing these that
> was missed off the original list? I'd like to get an idea
> if there is any work to do. I've got a couple more drivers
> to submit and was waiting on feedback from this before
> getting these submitted.


My overall feedback is: this isn't a pluggable device (PCI, etc),
so what's it intended to be used by? Generally we don't take
device models except when there's a board model that's using them.

thanks
-- PMM
Ben Dooks July 18, 2022, 10:25 a.m. UTC | #3
On 18/07/2022 11:15, Peter Maydell wrote:
> On Mon, 18 Jul 2022 at 11:05, Ben Dooks <ben.dooks@sifive.com> wrote:
>>
>> On 13/07/2022 18:20, Ben Dooks wrote:
>>> A model for the DesignWare GPIO (v1) block.
>>
>> Is there anyone else who should be reviewing these that
>> was missed off the original list? I'd like to get an idea
>> if there is any work to do. I've got a couple more drivers
>> to submit and was waiting on feedback from this before
>> getting these submitted.
> 
> 
> My overall feedback is: this isn't a pluggable device (PCI, etc),
> so what's it intended to be used by? Generally we don't take
> device models except when there's a board model that's using them.

I have a board file, but that's currently under NDA, so we're not
allowed to release it at the moment. However we've done a few drivers
which we'd like to get out of our development tree which other people
might find useful (GPIO, SPI, I2C).
Peter Maydell July 18, 2022, 10:38 a.m. UTC | #4
On Mon, 18 Jul 2022 at 11:25, Ben Dooks <ben.dooks@sifive.com> wrote:
>
> On 18/07/2022 11:15, Peter Maydell wrote:
> > On Mon, 18 Jul 2022 at 11:05, Ben Dooks <ben.dooks@sifive.com> wrote:
> >>
> >> On 13/07/2022 18:20, Ben Dooks wrote:
> >>> A model for the DesignWare GPIO (v1) block.
> >>
> >> Is there anyone else who should be reviewing these that
> >> was missed off the original list? I'd like to get an idea
> >> if there is any work to do. I've got a couple more drivers
> >> to submit and was waiting on feedback from this before
> >> getting these submitted.
> >
> >
> > My overall feedback is: this isn't a pluggable device (PCI, etc),
> > so what's it intended to be used by? Generally we don't take
> > device models except when there's a board model that's using them.
>
> I have a board file, but that's currently under NDA, so we're not
> allowed to release it at the moment. However we've done a few drivers
> which we'd like to get out of our development tree which other people
> might find useful (GPIO, SPI, I2C).

As I say, we don't really accept those, because we have no way
of testing them upstream unless they're used in a board file:
they're dead code from our point of view. When you have a board
model you're ready to submit you can send them in the same
patchseries as the board model.

thanks
-- PMM
diff mbox series

Patch

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 */