summaryrefslogtreecommitdiff
path: root/hw/gpio/mpc8xxx.c
diff options
context:
space:
mode:
authorAlexander Graf <agraf@suse.de>2014-10-01 15:52:12 +0200
committerAlexander Graf <agraf@suse.de>2014-11-04 23:26:12 +0100
commit228aa992fc5be408888c423b6a5b30daf18a96cf (patch)
treeff243ae76912ce9fdda9e71bc09061e2ed983450 /hw/gpio/mpc8xxx.c
parent81f194dd69756677cc36ff0827bf970f0f048914 (diff)
PPC: Add MPC8XXX gpio controller
On e500 systems most SoCs implement a common GPIO controller that Linux calls the "mpc8xxx" gpio controller. This patch adds an emulation model for this device. Signed-off-by: Alexander Graf <agraf@suse.de>
Diffstat (limited to 'hw/gpio/mpc8xxx.c')
-rw-r--r--hw/gpio/mpc8xxx.c217
1 files changed, 217 insertions, 0 deletions
diff --git a/hw/gpio/mpc8xxx.c b/hw/gpio/mpc8xxx.c
new file mode 100644
index 0000000000..1aeaaaaf03
--- /dev/null
+++ b/hw/gpio/mpc8xxx.c
@@ -0,0 +1,217 @@
+/*
+ * GPIO Controller for a lot of Freescale SoCs
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Alexander Graf, <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+
+#define TYPE_MPC8XXX_GPIO "mpc8xxx_gpio"
+#define MPC8XXX_GPIO(obj) OBJECT_CHECK(MPC8XXXGPIOState, (obj), TYPE_MPC8XXX_GPIO)
+
+typedef struct MPC8XXXGPIOState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq out[32];
+
+ uint32_t dir;
+ uint32_t odr;
+ uint32_t dat;
+ uint32_t ier;
+ uint32_t imr;
+ uint32_t icr;
+} MPC8XXXGPIOState;
+
+static const VMStateDescription vmstate_mpc8xxx_gpio = {
+ .name = "mpc8xxx_gpio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(dir, MPC8XXXGPIOState),
+ VMSTATE_UINT32(odr, MPC8XXXGPIOState),
+ VMSTATE_UINT32(dat, MPC8XXXGPIOState),
+ VMSTATE_UINT32(ier, MPC8XXXGPIOState),
+ VMSTATE_UINT32(imr, MPC8XXXGPIOState),
+ VMSTATE_UINT32(icr, MPC8XXXGPIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mpc8xxx_gpio_update(MPC8XXXGPIOState *s)
+{
+ qemu_set_irq(s->irq, !!(s->ier & s->imr));
+}
+
+static uint64_t mpc8xxx_gpio_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ MPC8XXXGPIOState *s = (MPC8XXXGPIOState *)opaque;
+
+ if (size != 4) {
+ /* All registers are 32bit */
+ return 0;
+ }
+
+ switch (offset) {
+ case 0x0: /* Direction */
+ return s->dir;
+ case 0x4: /* Open Drain */
+ return s->odr;
+ case 0x8: /* Data */
+ return s->dat;
+ case 0xC: /* Interrupt Event */
+ return s->ier;
+ case 0x10: /* Interrupt Mask */
+ return s->imr;
+ case 0x14: /* Interrupt Control */
+ return s->icr;
+ default:
+ return 0;
+ }
+}
+
+static void mpc8xxx_write_data(MPC8XXXGPIOState *s, uint32_t new_data)
+{
+ uint32_t old_data = s->dat;
+ uint32_t diff = old_data ^ new_data;
+ int i;
+
+ for (i = 0; i < 32; i++) {
+ uint32_t mask = 0x80000000 >> i;
+ if (!(diff & mask)) {
+ continue;
+ }
+
+ if (s->dir & mask) {
+ /* Output */
+ qemu_set_irq(s->out[i], (new_data & mask) != 0);
+ }
+ }
+
+ s->dat = new_data;
+}
+
+static void mpc8xxx_gpio_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ MPC8XXXGPIOState *s = (MPC8XXXGPIOState *)opaque;
+
+ if (size != 4) {
+ /* All registers are 32bit */
+ return;
+ }
+
+ switch (offset) {
+ case 0x0: /* Direction */
+ s->dir = value;
+ break;
+ case 0x4: /* Open Drain */
+ s->odr = value;
+ break;
+ case 0x8: /* Data */
+ mpc8xxx_write_data(s, value);
+ break;
+ case 0xC: /* Interrupt Event */
+ s->ier &= ~value;
+ break;
+ case 0x10: /* Interrupt Mask */
+ s->imr = value;
+ break;
+ case 0x14: /* Interrupt Control */
+ s->icr = value;
+ break;
+ }
+
+ mpc8xxx_gpio_update(s);
+}
+
+static void mpc8xxx_gpio_reset(MPC8XXXGPIOState *s)
+{
+ s->dir = 0;
+ s->odr = 0;
+ s->dat = 0;
+ s->ier = 0;
+ s->imr = 0;
+ s->icr = 0;
+}
+
+static void mpc8xxx_gpio_set_irq(void * opaque, int irq, int level)
+{
+ MPC8XXXGPIOState *s = (MPC8XXXGPIOState *)opaque;
+ uint32_t mask;
+
+ mask = 0x80000000 >> irq;
+ if ((s->dir & mask) == 0) {
+ uint32_t old_value = s->dat & mask;
+
+ s->dat &= ~mask;
+ if (level)
+ s->dat |= mask;
+
+ if (!(s->icr & irq) || (old_value && !level)) {
+ s->ier |= mask;
+ }
+
+ mpc8xxx_gpio_update(s);
+ }
+}
+
+static const MemoryRegionOps mpc8xxx_gpio_ops = {
+ .read = mpc8xxx_gpio_read,
+ .write = mpc8xxx_gpio_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+};
+
+static int mpc8xxx_gpio_initfn(SysBusDevice *sbd)
+{
+ DeviceState *dev = DEVICE(sbd);
+ MPC8XXXGPIOState *s = MPC8XXX_GPIO(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &mpc8xxx_gpio_ops, s, "mpc8xxx_gpio", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ qdev_init_gpio_in(dev, mpc8xxx_gpio_set_irq, 32);
+ qdev_init_gpio_out(dev, s->out, 32);
+ mpc8xxx_gpio_reset(s);
+ return 0;
+}
+
+static void mpc8xxx_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = mpc8xxx_gpio_initfn;
+ dc->vmsd = &vmstate_mpc8xxx_gpio;
+}
+
+static const TypeInfo mpc8xxx_gpio_info = {
+ .name = TYPE_MPC8XXX_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MPC8XXXGPIOState),
+ .class_init = mpc8xxx_gpio_class_init,
+};
+
+static void mpc8xxx_gpio_register_types(void)
+{
+ type_register_static(&mpc8xxx_gpio_info);
+}
+
+type_init(mpc8xxx_gpio_register_types)