diff mbox

[v7,10/12] register: Add GPIO API

Message ID 9372ab2e4771de7eb61079d68e53a2cd71c9bdcf.1466626975.git.alistair.francis@xilinx.com (mailing list archive)
State New, archived
Headers show

Commit Message

Alistair Francis June 22, 2016, 8:24 p.m. UTC
Add GPIO functionality to the register API. This allows association
and automatic connection of GPIOs to bits in registers. GPIO inputs
will attach to handlers that automatically set read-only bits in
registers. GPIO outputs will be updated to reflect their field value
when their respective registers are written (or reset). Supports
active low GPIOs.

This is particularly effective for implementing system level
controllers, where heterogenous collections of control signals are
placed is a SoC specific peripheral then propagated all over the
system.

Signed-off-by: Peter Crosthwaite <peter.crosthwaite@xilinx.com>
[ EI Changes:
  * register: Add a polarity field to GPIO connections
              Makes it possible to directly connect active low signals
              to generic interrupt pins.
]
Signed-off-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
Signed-off-by: Alistair Francis <alistair.francis@xilinx.com>
---
V6:
 - Don't use qdev_pass_all_gpios() as it doesn't seem to work
V5
 - Remove RegisterAccessError struct

 hw/core/register.c    | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++
 include/hw/register.h | 27 ++++++++++++++
 2 files changed, 124 insertions(+)

Comments

Peter Maydell June 23, 2016, 1:19 p.m. UTC | #1
On 22 June 2016 at 21:24, Alistair Francis <alistair.francis@xilinx.com> wrote:
> Add GPIO functionality to the register API. This allows association
> and automatic connection of GPIOs to bits in registers. GPIO inputs
> will attach to handlers that automatically set read-only bits in
> registers. GPIO outputs will be updated to reflect their field value
> when their respective registers are written (or reset). Supports
> active low GPIOs.
>
> This is particularly effective for implementing system level
> controllers, where heterogenous collections of control signals are
> placed is a SoC specific peripheral then propagated all over the
> system.

I still don't think this is a good idea. GPIOs are device-level,
not register-level. Most devices don't really have very many
GPIOs. It should be easy to wire up "change GPIO state based on
new register value" from a register post-write hook. This API
can't handle the absolute most common case, which is "GPIO state
is based on a bit being set in (raw-state-register AND mask-register)."

Basically I don't think GPIOs belong in this API, and I don't think
there's a good case that adding them makes a significant class
of devices much easier to write.

thanks
-- PMM
Alistair Francis June 23, 2016, 6:14 p.m. UTC | #2
On Thu, Jun 23, 2016 at 6:19 AM, Peter Maydell <peter.maydell@linaro.org> wrote:
> On 22 June 2016 at 21:24, Alistair Francis <alistair.francis@xilinx.com> wrote:
>> Add GPIO functionality to the register API. This allows association
>> and automatic connection of GPIOs to bits in registers. GPIO inputs
>> will attach to handlers that automatically set read-only bits in
>> registers. GPIO outputs will be updated to reflect their field value
>> when their respective registers are written (or reset). Supports
>> active low GPIOs.
>>
>> This is particularly effective for implementing system level
>> controllers, where heterogenous collections of control signals are
>> placed is a SoC specific peripheral then propagated all over the
>> system.
>
> I still don't think this is a good idea. GPIOs are device-level,
> not register-level. Most devices don't really have very many
> GPIOs. It should be easy to wire up "change GPIO state based on
> new register value" from a register post-write hook. This API
> can't handle the absolute most common case, which is "GPIO state
> is based on a bit being set in (raw-state-register AND mask-register)."
>
> Basically I don't think GPIOs belong in this API, and I don't think
> there's a good case that adding them makes a significant class
> of devices much easier to write.

Ok, I'm going to remove the GPIO part from this patch set (the last
few patches).

Once this patchset is accepted I'll spin up a new series with the
GPIOs and some better/more complex devices and we can go from there.

Thanks,

Alistair

>
> thanks
> -- PMM
>
diff mbox

Patch

diff --git a/hw/core/register.c b/hw/core/register.c
index f32faac..476c5db 100644
--- a/hw/core/register.c
+++ b/hw/core/register.c
@@ -108,6 +108,7 @@  void register_write(RegisterInfo *reg, uint64_t val, uint64_t we,
     }
 
     register_write_val(reg, new_val);
+    register_refresh_gpios(reg, old_val, debug);
 
     if (ac->post_write) {
         ac->post_write(reg, new_val);
@@ -147,23 +148,119 @@  uint64_t register_read(RegisterInfo *reg, const char* prefix, bool debug)
 void register_reset(RegisterInfo *reg)
 {
     g_assert(reg);
+    uint64_t old_val;
 
     if (!reg->data || !reg->access) {
         return;
     }
 
+    old_val = register_read_val(reg);
+
     register_write_val(reg, reg->access->reset);
+    register_refresh_gpios(reg, old_val, false);
+}
+
+void register_refresh_gpios(RegisterInfo *reg, uint64_t old_value, bool debug)
+{
+    const RegisterAccessInfo *ac;
+    const RegisterGPIOMapping *gpio;
+
+    ac = reg->access;
+    for (gpio = ac->gpios; gpio && gpio->name; gpio++) {
+        int i;
+
+        if (gpio->input) {
+            continue;
+        }
+
+        for (i = 0; i < gpio->num; ++i) {
+            uint64_t gpio_value, gpio_value_old;
+
+            qemu_irq gpo = qdev_get_gpio_out_connector(DEVICE(reg),
+                                                       gpio->name, i);
+            gpio_value_old = extract64(old_value,
+                                       gpio->bit_pos + i * gpio->width,
+                                       gpio->width) ^ gpio->polarity;
+            gpio_value = extract64(register_read_val(reg),
+                                   gpio->bit_pos + i * gpio->width,
+                                   gpio->width) ^ gpio->polarity;
+            if (!(gpio_value_old ^ gpio_value)) {
+                continue;
+            }
+            if (debug && gpo) {
+                qemu_log("refreshing gpio out %s to %" PRIx64 "\n",
+                         gpio->name, gpio_value);
+            }
+            qemu_set_irq(gpo, gpio_value);
+        }
+    }
+}
+
+typedef struct DeviceNamedGPIOHandlerOpaque {
+    DeviceState *dev;
+    const char *name;
+} DeviceNamedGPIOHandlerOpaque;
+
+static void register_gpio_handler(void *opaque, int n, int level)
+{
+    DeviceNamedGPIOHandlerOpaque *gho = opaque;
+    RegisterInfo *reg = REGISTER(gho->dev);
+
+    const RegisterAccessInfo *ac;
+    const RegisterGPIOMapping *gpio;
+
+    ac = reg->access;
+    for (gpio = ac->gpios; gpio && gpio->name; gpio++) {
+        if (gpio->input && !strcmp(gho->name, gpio->name)) {
+            register_write_val(reg, deposit64(register_read_val(reg),
+                                              gpio->bit_pos + n * gpio->width,
+                                              gpio->width,
+                                              level ^ gpio->polarity));
+            return;
+        }
+    }
+
+    abort();
 }
 
 void register_init(RegisterInfo *reg)
 {
     assert(reg);
+    const RegisterAccessInfo *ac;
+    const RegisterGPIOMapping *gpio;
 
     if (!reg->data || !reg->access) {
         return;
     }
 
     object_initialize((void *)reg, sizeof(*reg), TYPE_REGISTER);
+
+    ac = reg->access;
+    for (gpio = ac->gpios; gpio && gpio->name; gpio++) {
+        if (!gpio->num) {
+            ((RegisterGPIOMapping *)gpio)->num = 1;
+        }
+        if (!gpio->width) {
+            ((RegisterGPIOMapping *)gpio)->width = 1;
+        }
+        if (gpio->input) {
+            DeviceNamedGPIOHandlerOpaque gho = {
+                .name = gpio->name,
+                .dev = DEVICE(reg),
+            };
+            qemu_irq irq;
+
+            qdev_init_gpio_in_named(DEVICE(reg), register_gpio_handler,
+                                    gpio->name, gpio->num);
+            irq = qdev_get_gpio_in_named(DEVICE(reg), gpio->name, gpio->num);
+            qemu_irq_set_opaque(irq, g_memdup(&gho, sizeof(gho)));
+        } else {
+            qemu_irq *gpos = g_new0(qemu_irq, gpio->num);
+
+            qdev_init_gpio_out_named(DEVICE(reg), gpos, gpio->name, gpio->num);
+            qdev_pass_gpios(DEVICE(reg), reg->opaque, gpio->name);
+        }
+    }
 }
 
 void register_write_memory(void *opaque, hwaddr addr,
diff --git a/include/hw/register.h b/include/hw/register.h
index 3e4d1ae..112ca7b 100644
--- a/include/hw/register.h
+++ b/include/hw/register.h
@@ -13,11 +13,24 @@ 
 
 #include "hw/qdev-core.h"
 #include "exec/memory.h"
+#include "hw/irq.h"
 
 typedef struct RegisterInfo RegisterInfo;
 typedef struct RegisterAccessInfo RegisterAccessInfo;
 typedef struct RegisterInfoArray RegisterInfoArray;
 
+#define REG_GPIO_POL_HIGH 0
+#define REG_GPIO_POL_LOW  1
+
+typedef struct RegisterGPIOMapping {
+    const char *name;
+    uint8_t bit_pos;
+    bool input;
+    bool polarity;
+    uint8_t num;
+    uint8_t width;
+} RegisterGPIOMapping;
+
 /**
  * Access description for a register that is part of guest accessible device
  * state.
@@ -55,6 +68,8 @@  struct RegisterAccessInfo {
     uint64_t (*post_read)(RegisterInfo *reg, uint64_t val);
 
     hwaddr addr;
+
+    const RegisterGPIOMapping *gpios;
 };
 
 /**
@@ -148,6 +163,18 @@  void register_reset(RegisterInfo *reg);
 void register_init(RegisterInfo *reg);
 
 /**
+ * Refresh GPIO outputs based on diff between old value register current value.
+ * GPIOs are refreshed for fields where the old value differs to the current
+ * value.
+ *
+ * @reg: Register to refresh GPIO outs
+ * @old_value: previous value of register
+ * @debug: Should the read operation debug information be printed?
+ */
+
+void register_refresh_gpios(RegisterInfo *reg, uint64_t old_value, bool debug);
+
+/**
  * Memory API MMIO write handler that will write to a Register API register.
  * @opaque: RegisterInfo to write to
  * @addr: Address to write