| /* |
| * TI OMAP processors GPIO emulation. |
| * |
| * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org> |
| * Copyright (C) 2007-2009 Nokia Corporation |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 or |
| * (at your option) version 3 of the License. |
| * |
| * This program 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 General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "qemu/log.h" |
| #include "hw/irq.h" |
| #include "hw/qdev-properties.h" |
| #include "hw/arm/omap.h" |
| #include "hw/sysbus.h" |
| #include "qemu/error-report.h" |
| #include "qemu/module.h" |
| #include "qapi/error.h" |
| |
| struct omap_gpio_s { |
| qemu_irq irq; |
| qemu_irq handler[16]; |
| |
| uint16_t inputs; |
| uint16_t outputs; |
| uint16_t dir; |
| uint16_t edge; |
| uint16_t mask; |
| uint16_t ints; |
| uint16_t pins; |
| }; |
| |
| struct Omap1GpioState { |
| SysBusDevice parent_obj; |
| |
| MemoryRegion iomem; |
| int mpu_model; |
| void *clk; |
| struct omap_gpio_s omap1; |
| }; |
| |
| /* General-Purpose I/O of OMAP1 */ |
| static void omap_gpio_set(void *opaque, int line, int level) |
| { |
| Omap1GpioState *p = opaque; |
| struct omap_gpio_s *s = &p->omap1; |
| uint16_t prev = s->inputs; |
| |
| if (level) |
| s->inputs |= 1 << line; |
| else |
| s->inputs &= ~(1 << line); |
| |
| if (((s->edge & s->inputs & ~prev) | (~s->edge & ~s->inputs & prev)) & |
| (1 << line) & s->dir & ~s->mask) { |
| s->ints |= 1 << line; |
| qemu_irq_raise(s->irq); |
| } |
| } |
| |
| static uint64_t omap_gpio_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| struct omap_gpio_s *s = opaque; |
| int offset = addr & OMAP_MPUI_REG_MASK; |
| |
| if (size != 2) { |
| return omap_badwidth_read16(opaque, addr); |
| } |
| |
| switch (offset) { |
| case 0x00: /* DATA_INPUT */ |
| return s->inputs & s->pins; |
| |
| case 0x04: /* DATA_OUTPUT */ |
| return s->outputs; |
| |
| case 0x08: /* DIRECTION_CONTROL */ |
| return s->dir; |
| |
| case 0x0c: /* INTERRUPT_CONTROL */ |
| return s->edge; |
| |
| case 0x10: /* INTERRUPT_MASK */ |
| return s->mask; |
| |
| case 0x14: /* INTERRUPT_STATUS */ |
| return s->ints; |
| |
| case 0x18: /* PIN_CONTROL (not in OMAP310) */ |
| OMAP_BAD_REG(addr); |
| return s->pins; |
| } |
| |
| OMAP_BAD_REG(addr); |
| return 0; |
| } |
| |
| static void omap_gpio_write(void *opaque, hwaddr addr, |
| uint64_t value, unsigned size) |
| { |
| struct omap_gpio_s *s = opaque; |
| int offset = addr & OMAP_MPUI_REG_MASK; |
| uint16_t diff; |
| int ln; |
| |
| if (size != 2) { |
| omap_badwidth_write16(opaque, addr, value); |
| return; |
| } |
| |
| switch (offset) { |
| case 0x00: /* DATA_INPUT */ |
| OMAP_RO_REG(addr); |
| return; |
| |
| case 0x04: /* DATA_OUTPUT */ |
| diff = (s->outputs ^ value) & ~s->dir; |
| s->outputs = value; |
| while ((ln = ctz32(diff)) != 32) { |
| if (s->handler[ln]) |
| qemu_set_irq(s->handler[ln], (value >> ln) & 1); |
| diff &= ~(1 << ln); |
| } |
| break; |
| |
| case 0x08: /* DIRECTION_CONTROL */ |
| diff = s->outputs & (s->dir ^ value); |
| s->dir = value; |
| |
| value = s->outputs & ~s->dir; |
| while ((ln = ctz32(diff)) != 32) { |
| if (s->handler[ln]) |
| qemu_set_irq(s->handler[ln], (value >> ln) & 1); |
| diff &= ~(1 << ln); |
| } |
| break; |
| |
| case 0x0c: /* INTERRUPT_CONTROL */ |
| s->edge = value; |
| break; |
| |
| case 0x10: /* INTERRUPT_MASK */ |
| s->mask = value; |
| break; |
| |
| case 0x14: /* INTERRUPT_STATUS */ |
| s->ints &= ~value; |
| if (!s->ints) |
| qemu_irq_lower(s->irq); |
| break; |
| |
| case 0x18: /* PIN_CONTROL (not in OMAP310 TRM) */ |
| OMAP_BAD_REG(addr); |
| s->pins = value; |
| break; |
| |
| default: |
| OMAP_BAD_REG(addr); |
| return; |
| } |
| } |
| |
| /* *Some* sources say the memory region is 32-bit. */ |
| static const MemoryRegionOps omap_gpio_ops = { |
| .read = omap_gpio_read, |
| .write = omap_gpio_write, |
| .endianness = DEVICE_NATIVE_ENDIAN, |
| }; |
| |
| static void omap_gpio_reset(struct omap_gpio_s *s) |
| { |
| s->inputs = 0; |
| s->outputs = ~0; |
| s->dir = ~0; |
| s->edge = ~0; |
| s->mask = ~0; |
| s->ints = 0; |
| s->pins = ~0; |
| } |
| |
| static void omap_gpif_reset(DeviceState *dev) |
| { |
| Omap1GpioState *s = OMAP1_GPIO(dev); |
| |
| omap_gpio_reset(&s->omap1); |
| } |
| |
| static void omap_gpio_init(Object *obj) |
| { |
| DeviceState *dev = DEVICE(obj); |
| Omap1GpioState *s = OMAP1_GPIO(obj); |
| SysBusDevice *sbd = SYS_BUS_DEVICE(obj); |
| |
| qdev_init_gpio_in(dev, omap_gpio_set, 16); |
| qdev_init_gpio_out(dev, s->omap1.handler, 16); |
| sysbus_init_irq(sbd, &s->omap1.irq); |
| memory_region_init_io(&s->iomem, obj, &omap_gpio_ops, &s->omap1, |
| "omap.gpio", 0x1000); |
| sysbus_init_mmio(sbd, &s->iomem); |
| } |
| |
| static void omap_gpio_realize(DeviceState *dev, Error **errp) |
| { |
| Omap1GpioState *s = OMAP1_GPIO(dev); |
| |
| if (!s->clk) { |
| error_setg(errp, "omap-gpio: clk not connected"); |
| } |
| } |
| |
| void omap_gpio_set_clk(Omap1GpioState *gpio, omap_clk clk) |
| { |
| gpio->clk = clk; |
| } |
| |
| static Property omap_gpio_properties[] = { |
| DEFINE_PROP_INT32("mpu_model", Omap1GpioState, mpu_model, 0), |
| DEFINE_PROP_END_OF_LIST(), |
| }; |
| |
| static void omap_gpio_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->realize = omap_gpio_realize; |
| device_class_set_legacy_reset(dc, omap_gpif_reset); |
| device_class_set_props(dc, omap_gpio_properties); |
| /* Reason: pointer property "clk" */ |
| dc->user_creatable = false; |
| } |
| |
| static const TypeInfo omap_gpio_info = { |
| .name = TYPE_OMAP1_GPIO, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(Omap1GpioState), |
| .instance_init = omap_gpio_init, |
| .class_init = omap_gpio_class_init, |
| }; |
| |
| static void omap_gpio_register_types(void) |
| { |
| type_register_static(&omap_gpio_info); |
| } |
| |
| type_init(omap_gpio_register_types) |