* [Qemu-devel] [PATCH V2 0/3] Exynos: i2c, gpio and touchscreen support for NURI board
@ 2012-03-15 7:35 Igor Mitsyanko
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation Igor Mitsyanko
` (2 more replies)
0 siblings, 3 replies; 18+ messages in thread
From: Igor Mitsyanko @ 2012-03-15 7:35 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, e.voevodin, kyungmin.park, d.solodkiy, m.kozlov,
afaerber
This patchset adds Exynos4210 i2c and GPIO implementation along with Atmel MXT touchscreen
which is used for NURI board emulation.
v1->v2
- QOM-related patches are droped (they have already been accepted);
- Added indendations for second and subsequent lines of multiple-line macro definitions;
- Weird big spaces after .field members of VMStateDescriptions are replaced with single space;
- maxtouch.c is not ARM target specific from now on.
Igor Mitsyanko (3):
exynos4210: add Exynos4210 i2c implementation
exynos4210: add exynos4210 GPIO implementation
hw: add Atmel maxtouch touchscreen implementation
Makefile.objs | 1 +
Makefile.target | 1 +
default-configs/arm-softmmu.mak | 1 +
hw/exynos4210.c | 72 +++
hw/exynos4210.h | 67 +++
hw/exynos4210_gpio.c | 1117 +++++++++++++++++++++++++++++++++++++++
hw/exynos4210_i2c.c | 469 ++++++++++++++++
hw/exynos4_boards.c | 13 +-
hw/maxtouch.c | 1079 +++++++++++++++++++++++++++++++++++++
9 files changed, 2816 insertions(+), 4 deletions(-)
create mode 100644 hw/exynos4210_gpio.c
create mode 100644 hw/exynos4210_i2c.c
create mode 100644 hw/maxtouch.c
--
1.7.4.1
^ permalink raw reply [flat|nested] 18+ messages in thread
* [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation
2012-03-15 7:35 [Qemu-devel] [PATCH V2 0/3] Exynos: i2c, gpio and touchscreen support for NURI board Igor Mitsyanko
@ 2012-03-15 7:35 ` Igor Mitsyanko
2012-03-15 10:35 ` Andreas Färber
` (2 more replies)
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 2/3] exynos4210: add exynos4210 GPIO implementation Igor Mitsyanko
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 3/3] hw: add Atmel maxtouch touchscreen implementation Igor Mitsyanko
2 siblings, 3 replies; 18+ messages in thread
From: Igor Mitsyanko @ 2012-03-15 7:35 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, Igor Mitsyanko, e.voevodin, kyungmin.park,
d.solodkiy, m.kozlov, afaerber
Create 9 exynos4210 i2c interfaces.
Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
---
Makefile.target | 1 +
hw/exynos4210.c | 26 +++
hw/exynos4210.h | 3 +
hw/exynos4210_i2c.c | 469 +++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 499 insertions(+), 0 deletions(-)
create mode 100644 hw/exynos4210_i2c.c
diff --git a/Makefile.target b/Makefile.target
index eb25941..5e8a1d4 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -357,6 +357,7 @@ obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
obj-arm-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
obj-arm-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o
+obj-arm-y += exynos4210_i2c.o
obj-arm-y += arm_l2x0.o
obj-arm-y += arm_mptimer.o a15mpcore.o
obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index f904370..464f157 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -35,6 +35,13 @@
/* MCT */
#define EXYNOS4210_MCT_BASE_ADDR 0x10050000
+/* I2C */
+#define EXYNOS4210_I2C_SHIFT 0x00010000
+#define EXYNOS4210_I2C_BASE_ADDR 0x13860000
+/* Interrupt Group of External Interrupt Combiner for I2C */
+#define EXYNOS4210_I2C_INTG 27
+#define EXYNOS4210_HDMI_INTG 16
+
/* UART's definitions */
#define EXYNOS4210_UART0_BASE_ADDR 0x13800000
#define EXYNOS4210_UART1_BASE_ADDR 0x13810000
@@ -242,6 +249,25 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
s->irq_table[exynos4210_get_irq(35, 3)]);
sysbus_mmio_map(busdev, 0, EXYNOS4210_MCT_BASE_ADDR);
+ /*** I2C ***/
+ for (n = 0; n < EXYNOS4210_I2C_NUMBER; n++) {
+ uint32_t addr = EXYNOS4210_I2C_BASE_ADDR + EXYNOS4210_I2C_SHIFT * n;
+ qemu_irq i2c_irq;
+
+ if (n < 8) {
+ i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_I2C_INTG, n)];
+ } else {
+ i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_HDMI_INTG, 1)];
+ }
+
+ dev = qdev_create(NULL, "exynos4210.i2c");
+ qdev_init_nofail(dev);
+ busdev = sysbus_from_qdev(dev);
+ sysbus_connect_irq(busdev, 0, i2c_irq);
+ sysbus_mmio_map(busdev, 0, addr);
+ s->i2c_if[n] = (i2c_bus *)qdev_get_child_bus(dev, "i2c");
+ }
+
/*** UARTs ***/
exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR,
EXYNOS4210_UART0_FIFO_SIZE, 0, NULL,
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index c112e03..1205fb5 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -74,6 +74,8 @@
#define EXYNOS4210_EXT_GIC_NIRQ (160-32)
#define EXYNOS4210_INT_GIC_NIRQ 64
+#define EXYNOS4210_I2C_NUMBER 9
+
typedef struct Exynos4210Irq {
qemu_irq int_combiner_irq[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ];
qemu_irq ext_combiner_irq[EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ];
@@ -95,6 +97,7 @@ typedef struct Exynos4210State {
MemoryRegion dram1_mem;
MemoryRegion boot_secondary;
MemoryRegion bootreg_mem;
+ i2c_bus *i2c_if[EXYNOS4210_I2C_NUMBER];
} Exynos4210State;
Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
diff --git a/hw/exynos4210_i2c.c b/hw/exynos4210_i2c.c
new file mode 100644
index 0000000..42f770d
--- /dev/null
+++ b/hw/exynos4210_i2c.c
@@ -0,0 +1,469 @@
+/*
+ * Exynos4210 I2C Bus Serial Interface Emulation
+ *
+ * Copyright (C) 2012 Samsung Electronics Co Ltd.
+ * Maksim Kozlov, <m.kozlov@samsung.com>
+ * Igor Mitsyanko, <i.mitsyanko@samsung.com>
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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-timer.h"
+#include "sysbus.h"
+#include "i2c.h"
+
+#ifndef EXYNOS4_I2C_DEBUG
+#define EXYNOS4_I2C_DEBUG 0
+#endif
+
+#define TYPE_EXYNOS4_I2C "exynos4210.i2c"
+#define TYPE_EXYNOS4_I2C_SLAVE "exynos4210.i2c-slave"
+#define EXYNOS4_I2C(obj) \
+ OBJECT_CHECK(Exynos4210I2CState, (obj), TYPE_EXYNOS4_I2C)
+#define EXYNOS4_I2C_SLAVE(obj) \
+ OBJECT_CHECK(Exynos4210I2CSlave, (obj), TYPE_EXYNOS4_I2C_SLAVE)
+
+/* Exynos4210 I2C memory map */
+#define EXYNOS4_I2C_MEM_SIZE 0x14
+#define I2CCON_ADDR 0x00 /* control register */
+#define I2CSTAT_ADDR 0x04 /* control/status register */
+#define I2CADD_ADDR 0x08 /* address register */
+#define I2CDS_ADDR 0x0c /* data shift register */
+#define I2CLC_ADDR 0x10 /* line control register */
+
+#define I2CCON_INTERRUPTS_EN (1 << 5)
+#define I2CCON_INTERRUPT_PEND (1 << 4)
+
+#define EXYNOS4_I2C_MODE(reg) (((reg) >> 6) & 3)
+#define I2C_IN_MASTER_MODE(reg) (((reg) >> 6) & 2)
+#define I2CSTAT_MODE_SLAVE_Rx 0x0
+#define I2CSTAT_MODE_SLAVE_Tx 0x1
+#define I2CMODE_MASTER_Rx 0x2
+#define I2CMODE_MASTER_Tx 0x3
+#define I2CSTAT_LAST_BIT (1 << 0)
+#define I2CSTAT_SLAVE_ADDR_ZERO (1 << 1)
+#define I2CSTAT_SLAVE_ADDR_MATCH (1 << 2)
+#define I2CSTAT_ARBITR_STATUS (1 << 3)
+#define I2CSTAT_OUTPUT_EN (1 << 4)
+#define I2CSTAT_START_BUSY (1 << 5)
+
+
+#if EXYNOS4_I2C_DEBUG
+#define DPRINT(fmt, args...) \
+ do {fprintf(stderr, "QEMU I2C: "fmt, ## args); } while (0)
+
+static const char *exynos4_i2c_regs_names[] = {
+ [I2CCON_ADDR >> 2] = "I2CCON",
+ [I2CSTAT_ADDR >> 2] = "I2CSTAT",
+ [I2CADD_ADDR >> 2] = "I2CADD",
+ [I2CDS_ADDR >> 2] = "I2CDS",
+ [I2CLC_ADDR >> 2] = "I2CLC",
+};
+
+#else
+#define DPRINT(fmt, args...) do { } while (0)
+#endif
+
+typedef struct Exynos4210I2CSlave Exynos4210I2CSlave;
+
+typedef struct Exynos4210I2CState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+ i2c_bus *bus;
+ Exynos4210I2CSlave *slave;
+ qemu_irq irq;
+ QEMUTimer *receive_timer;
+ QEMUTimer *send_timer;
+
+ uint8_t i2ccon;
+ uint8_t i2cstat;
+ uint8_t i2cadd;
+ uint8_t i2cds;
+ uint8_t i2clc;
+} Exynos4210I2CState;
+
+enum {
+ idle,
+ start_bit_received,
+ sending_data,
+ receiving_data
+};
+
+struct Exynos4210I2CSlave {
+ I2CSlave i2c;
+ Exynos4210I2CState *host;
+ uint32_t status;
+};
+
+
+static inline void exynos4210_i2c_set_irq(Exynos4210I2CState *s)
+{
+ if (s->i2ccon & I2CCON_INTERRUPTS_EN) {
+ s->i2ccon |= I2CCON_INTERRUPT_PEND;
+ qemu_irq_raise(s->irq);
+ }
+}
+
+static void exynos4210_i2c_data_received(void *opaque)
+{
+ Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+ int ret;
+
+ ret = i2c_recv(s->bus);
+ if (ret < 0) {
+ s->i2cstat |= I2CSTAT_LAST_BIT; /* Data is not acknowledged */
+ } else {
+ s->i2cds = ret;
+ s->i2cstat &= ~I2CSTAT_LAST_BIT; /* Data is acknowledged */
+ }
+ exynos4210_i2c_set_irq(s);
+}
+
+static void exynos4210_i2c_data_sent(void *opaque)
+{
+ Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+
+ if (i2c_send(s->bus, s->i2cds) < 0) {
+ s->i2cstat |= I2CSTAT_LAST_BIT; /* Data is not acknowledged */
+ } else {
+ s->i2cstat &= ~I2CSTAT_LAST_BIT; /* Data is acknowledged */
+ }
+ exynos4210_i2c_set_irq(s);
+}
+
+static uint64_t exynos4210_i2c_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+ uint8_t value;
+
+ switch (offset) {
+ case I2CCON_ADDR:
+ value = s->i2ccon;
+ break;
+ case I2CSTAT_ADDR:
+ value = s->i2cstat;
+ break;
+ case I2CADD_ADDR:
+ value = s->i2cadd;
+ break;
+ case I2CDS_ADDR:
+ value = s->i2cds;
+ break;
+ case I2CLC_ADDR:
+ value = s->i2clc;
+ break;
+ default:
+ value = 0;
+ DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset);
+ break;
+ }
+
+ DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_regs_names[offset >> 2],
+ (unsigned int)offset, value);
+ return value;
+}
+
+static void exynos4210_i2c_write(void *opaque, target_phys_addr_t offset,
+ uint64_t value, unsigned size)
+{
+ Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
+ uint8_t v = value & 0xff;
+
+ DPRINT("write %s [0x%02x] <- 0x%02x\n", exynos4_i2c_regs_names[offset >> 2],
+ (unsigned int)offset, v);
+
+ switch (offset) {
+ case I2CCON_ADDR:
+ if ((s->i2ccon & I2CCON_INTERRUPT_PEND) &&
+ !(v & I2CCON_INTERRUPT_PEND)) {
+ s->i2ccon &= ~I2CCON_INTERRUPT_PEND;
+ if (!(s->i2ccon & I2CCON_INTERRUPTS_EN)) {
+ s->i2cstat &= ~I2CSTAT_START_BUSY;
+ }
+ qemu_irq_lower(s->irq);
+
+ if (!(s->i2cstat & I2CSTAT_OUTPUT_EN) ||
+ !(s->i2cstat & I2CSTAT_START_BUSY)) {
+ s->i2cstat &= ~I2CSTAT_START_BUSY; /* Line is not busy */
+ break;
+ }
+ /* Continue operation if transfer is ongoing */
+ if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) {
+ qemu_mod_timer(s->receive_timer, qemu_get_clock_ns(vm_clock)+1);
+ } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) {
+ qemu_mod_timer(s->send_timer, qemu_get_clock_ns(vm_clock) + 1);
+ }
+ }
+
+ /* bit 4 can't be written */
+ s->i2ccon = (v & ~I2CCON_INTERRUPT_PEND) |
+ (s->i2ccon & I2CCON_INTERRUPT_PEND);
+ break;
+ case I2CSTAT_ADDR:
+ s->i2cstat =
+ (s->i2cstat & I2CSTAT_START_BUSY) | (v & ~I2CSTAT_START_BUSY);
+
+ if (!(s->i2cstat & I2CSTAT_OUTPUT_EN)) {
+ s->i2cstat &= ~I2CSTAT_START_BUSY;
+ qemu_del_timer(s->receive_timer);
+ qemu_del_timer(s->send_timer);
+ break;
+ }
+
+ /* Nothing to do if in i2c slave mode */
+ if (!I2C_IN_MASTER_MODE(s->i2cstat)) {
+ break;
+ }
+
+ if (v & I2CSTAT_START_BUSY) {
+ s->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */
+ /* Generate start bit and send slave address */
+ i2c_start_transfer(s->bus, s->i2cds >> 1, s->i2cds & 0x1);
+ exynos4210_i2c_data_sent(s);
+ } else {
+ i2c_end_transfer(s->bus);
+ /* Linux driver may start next i2c transaction before it
+ * acknowledges an interrupt request from previous transaction.
+ * This may result in timeout waiting for I2C bus idle and
+ * corrupted transfer. Releasing I2C bus only after interrupt is
+ * accepted prevents this corruption. */
+ if (!(s->i2ccon & I2CCON_INTERRUPT_PEND)) {
+ s->i2cstat &= ~I2CSTAT_START_BUSY;
+ }
+ }
+ break;
+ case I2CADD_ADDR:
+ if ((s->i2cstat & I2CSTAT_OUTPUT_EN) == 0) {
+ s->i2cadd = v;
+ i2c_set_slave_address(&s->slave->i2c, v);
+ }
+ break;
+ case I2CDS_ADDR:
+ if (s->i2cstat & I2CSTAT_OUTPUT_EN) {
+ s->i2cds = v;
+ }
+ break;
+ case I2CLC_ADDR:
+ s->i2clc = v;
+ break;
+ default:
+ DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps exynos4210_i2c_ops = {
+ .read = exynos4210_i2c_read,
+ .write = exynos4210_i2c_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription exynos4210_i2c_slave_vmstate = {
+ .name = TYPE_EXYNOS4_I2C_SLAVE,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_I2C_SLAVE(i2c, Exynos4210I2CSlave),
+ VMSTATE_UINT32(status, Exynos4210I2CSlave),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription exynos4210_i2c_vmstate = {
+ .name = TYPE_EXYNOS4_I2C,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(i2ccon, Exynos4210I2CState),
+ VMSTATE_UINT8(i2cstat, Exynos4210I2CState),
+ VMSTATE_UINT8(i2cds, Exynos4210I2CState),
+ VMSTATE_UINT8(i2clc, Exynos4210I2CState),
+ VMSTATE_TIMER(receive_timer, Exynos4210I2CState),
+ VMSTATE_TIMER(send_timer, Exynos4210I2CState),
+ VMSTATE_STRUCT_POINTER(slave, Exynos4210I2CState,
+ exynos4210_i2c_slave_vmstate, Exynos4210I2CSlave *),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void exynos4210_i2c_reset(DeviceState *d)
+{
+ Exynos4210I2CState *s = EXYNOS4_I2C(d);
+
+ s->i2ccon = 0x0f; /* 0x0X */
+ s->i2cstat = 0x00;
+ s->i2cds = 0xff; /* 0xXX */
+ s->i2clc = 0x00;
+ s->i2cadd = 0xff; /* 0xXX */
+ s->slave->status = idle;
+ qemu_del_timer(s->receive_timer);
+ qemu_del_timer(s->send_timer);
+}
+
+static int exynos4210_i2c_init(SysBusDevice *dev)
+{
+ Exynos4210I2CState *s = EXYNOS4_I2C(dev);
+ DeviceState *slave;
+
+ memory_region_init_io(&s->iomem, &exynos4210_i2c_ops, s, TYPE_EXYNOS4_I2C,
+ EXYNOS4_I2C_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+ sysbus_init_irq(dev, &s->irq);
+ s->bus = i2c_init_bus(&dev->qdev, "i2c");
+ s->receive_timer =
+ qemu_new_timer_ns(vm_clock, exynos4210_i2c_data_received, s);
+ s->send_timer = qemu_new_timer_ns(vm_clock, exynos4210_i2c_data_sent, s);
+
+ /* i2c slave initialization */
+ slave = i2c_create_slave(s->bus, TYPE_EXYNOS4_I2C_SLAVE, 0xff);
+ s->slave = EXYNOS4_I2C_SLAVE(slave);
+ s->slave->host = s;
+
+ return 0;
+}
+
+static void exynos4210_i2c_class_init(ObjectClass *class, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(class);
+ SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(class);
+
+ dc->vmsd = &exynos4210_i2c_vmstate;
+ dc->reset = exynos4210_i2c_reset;
+ sbc->init = exynos4210_i2c_init;
+}
+
+static TypeInfo exynos4210_i2c_info = {
+ .name = TYPE_EXYNOS4_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210I2CState),
+ .class_init = exynos4210_i2c_class_init,
+};
+
+static int exynos4210_i2c_slave_init(I2CSlave *i2c)
+{
+ Exynos4210I2CSlave *s = EXYNOS4_I2C_SLAVE(i2c);
+
+ s->status = idle;
+ return 0;
+}
+
+static void exynos4210_i2c_slave_event(I2CSlave *i2c, enum i2c_event event)
+{
+ Exynos4210I2CSlave *s = EXYNOS4_I2C_SLAVE(i2c);
+ Exynos4210I2CState *host = s->host;
+
+ if (!(host->i2cstat & I2CSTAT_OUTPUT_EN) ||
+ I2C_IN_MASTER_MODE(host->i2cstat)) {
+ return;
+ }
+
+ switch (event) {
+ case I2C_START_RECV:
+ case I2C_START_SEND:
+ host->i2cstat &= ~(I2CSTAT_SLAVE_ADDR_ZERO | I2CSTAT_SLAVE_ADDR_MATCH);
+ s->status = start_bit_received;
+ host->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */
+ break;
+ case I2C_FINISH:
+ s->status = idle;
+ host->i2cstat &= ~(I2CSTAT_SLAVE_ADDR_ZERO | I2CSTAT_SLAVE_ADDR_MATCH |
+ I2CSTAT_START_BUSY);
+ break;
+ default:
+ break;
+ }
+}
+
+static int exynos4210_i2c_slave_recv(I2CSlave *i2c)
+{
+ Exynos4210I2CSlave *s = EXYNOS4_I2C_SLAVE(i2c);
+ Exynos4210I2CState *host = s->host;
+
+ if (!(host->i2cstat & I2CSTAT_OUTPUT_EN) ||
+ I2C_IN_MASTER_MODE(host->i2cstat)) {
+ return -1;
+ }
+
+ if ((s->status != sending_data) || (EXYNOS4_I2C_MODE(host->i2cstat) !=
+ I2CSTAT_MODE_SLAVE_Tx)) {
+ return -1;
+ }
+
+ exynos4210_i2c_set_irq(host);
+ return host->i2cds;
+}
+
+static int exynos4210_i2c_slave_send(I2CSlave *i2c, uint8_t data)
+{
+ Exynos4210I2CSlave *s = EXYNOS4_I2C_SLAVE(i2c);
+ Exynos4210I2CState *host = s->host;
+
+ if (!(host->i2cstat & I2CSTAT_OUTPUT_EN) ||
+ I2C_IN_MASTER_MODE(host->i2cstat)) {
+ return -1;
+ }
+
+ if (s->status == start_bit_received) {
+ host->i2cds = data;
+ s->status = idle;
+ if ((data & 0xFE) == 0) {
+ host->i2cstat |= I2CSTAT_SLAVE_ADDR_ZERO;
+ return -1;
+ }
+ if ((host->i2cadd & 0xFE) != (data & 0xFE)) {
+ return -1;
+ }
+ host->i2cstat |= I2CSTAT_SLAVE_ADDR_MATCH;
+ (data & 0x1) ? (s->status = receiving_data) :
+ (s->status = sending_data);
+ exynos4210_i2c_set_irq(host);
+ return 0;
+ }
+
+ if ((s->status != receiving_data) || (EXYNOS4_I2C_MODE(host->i2cstat) !=
+ I2CSTAT_MODE_SLAVE_Rx)) {
+ return -1;
+ }
+
+ host->i2cds = data;
+ exynos4210_i2c_set_irq(host);
+ return 0;
+}
+
+static void exynos4210_i2cslave_class_init(ObjectClass *klass, void *data)
+{
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->init = exynos4210_i2c_slave_init;
+ k->event = exynos4210_i2c_slave_event;
+ k->recv = exynos4210_i2c_slave_recv;
+ k->send = exynos4210_i2c_slave_send;
+}
+
+static TypeInfo exynos4210_i2c_slave = {
+ .name = TYPE_EXYNOS4_I2C_SLAVE,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(Exynos4210I2CSlave),
+ .class_init = exynos4210_i2cslave_class_init,
+};
+
+static void exynos4210_i2c_register_types(void)
+{
+ type_register_static(&exynos4210_i2c_slave);
+ type_register_static(&exynos4210_i2c_info);
+}
+
+type_init(exynos4210_i2c_register_types)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [Qemu-devel] [PATCH V2 2/3] exynos4210: add exynos4210 GPIO implementation
2012-03-15 7:35 [Qemu-devel] [PATCH V2 0/3] Exynos: i2c, gpio and touchscreen support for NURI board Igor Mitsyanko
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation Igor Mitsyanko
@ 2012-03-15 7:35 ` Igor Mitsyanko
2012-03-15 11:06 ` Andreas Färber
2012-03-20 13:27 ` Peter Maydell
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 3/3] hw: add Atmel maxtouch touchscreen implementation Igor Mitsyanko
2 siblings, 2 replies; 18+ messages in thread
From: Igor Mitsyanko @ 2012-03-15 7:35 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, Igor Mitsyanko, e.voevodin, kyungmin.park,
d.solodkiy, m.kozlov, afaerber
Now that we have GPIO emulation for exynos4210 SoC we can use it to
properly hook up IRQ line to lan9215 controller on SMDK board.
Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
---
Makefile.target | 2 +-
hw/exynos4210.c | 46 ++
hw/exynos4210.h | 64 +++
hw/exynos4210_gpio.c | 1117 ++++++++++++++++++++++++++++++++++++++++++++++++++
hw/exynos4_boards.c | 2 +-
5 files changed, 1229 insertions(+), 2 deletions(-)
create mode 100644 hw/exynos4210_gpio.c
diff --git a/Makefile.target b/Makefile.target
index 5e8a1d4..aab4ec6 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -357,7 +357,7 @@ obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
obj-arm-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
obj-arm-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o
-obj-arm-y += exynos4210_i2c.o
+obj-arm-y += exynos4210_i2c.o exynos4210_gpio.o
obj-arm-y += arm_l2x0.o
obj-arm-y += arm_mptimer.o a15mpcore.o
obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 464f157..d81fc3d 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -35,6 +35,12 @@
/* MCT */
#define EXYNOS4210_MCT_BASE_ADDR 0x10050000
+/* GPIO */
+#define EXYNOS4210_GPIO1_BASE_ADDR 0x11400000
+#define EXYNOS4210_GPIO2_BASE_ADDR 0x11000000
+#define EXYNOS4210_GPIO2X_BASE_ADDR 0x11000C00
+#define EXYNOS4210_GPIO3_BASE_ADDR 0x03860000
+
/* I2C */
#define EXYNOS4210_I2C_SHIFT 0x00010000
#define EXYNOS4210_I2C_BASE_ADDR 0x13860000
@@ -249,6 +255,46 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
s->irq_table[exynos4210_get_irq(35, 3)]);
sysbus_mmio_map(busdev, 0, EXYNOS4210_MCT_BASE_ADDR);
+ /* GPIO */
+ s->gpio1 = qdev_create(NULL, "exynos4210.gpio1");
+ qdev_init_nofail(s->gpio1);
+ busdev = sysbus_from_qdev(s->gpio1);
+ sysbus_connect_irq(busdev, 0, s->irq_table[exynos4210_get_irq(24, 1)]);
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_GPIO1_BASE_ADDR);
+
+ s->gpio2 = qdev_create(NULL, "exynos4210.gpio2");
+ qdev_init_nofail(s->gpio2);
+ busdev = sysbus_from_qdev(s->gpio2);
+ sysbus_connect_irq(busdev, 0, s->irq_table[exynos4210_get_irq(24, 0)]);
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_GPIO2_BASE_ADDR);
+
+ s->gpio2x = qdev_create(NULL, "exynos4210.gpio2x");
+ qdev_init_nofail(s->gpio2x);
+ busdev = sysbus_from_qdev(s->gpio2x);
+ sysbus_connect_irq(busdev, 0, s->irq_table[exynos4210_get_irq(40, 0)]);
+ sysbus_connect_irq(busdev, 1, s->irq_table[exynos4210_get_irq(41, 0)]);
+ sysbus_connect_irq(busdev, 2, s->irq_table[exynos4210_get_irq(42, 0)]);
+ sysbus_connect_irq(busdev, 3, s->irq_table[exynos4210_get_irq(43, 0)]);
+ sysbus_connect_irq(busdev, 4, s->irq_table[exynos4210_get_irq(37, 0)]);
+ sysbus_connect_irq(busdev, 5, s->irq_table[exynos4210_get_irq(37, 1)]);
+ sysbus_connect_irq(busdev, 6, s->irq_table[exynos4210_get_irq(37, 2)]);
+ sysbus_connect_irq(busdev, 7, s->irq_table[exynos4210_get_irq(37, 3)]);
+ sysbus_connect_irq(busdev, 8, s->irq_table[exynos4210_get_irq(38, 0)]);
+ sysbus_connect_irq(busdev, 9, s->irq_table[exynos4210_get_irq(38, 1)]);
+ sysbus_connect_irq(busdev, 10, s->irq_table[exynos4210_get_irq(38, 2)]);
+ sysbus_connect_irq(busdev, 11, s->irq_table[exynos4210_get_irq(38, 3)]);
+ sysbus_connect_irq(busdev, 12, s->irq_table[exynos4210_get_irq(38, 4)]);
+ sysbus_connect_irq(busdev, 13, s->irq_table[exynos4210_get_irq(38, 5)]);
+ sysbus_connect_irq(busdev, 14, s->irq_table[exynos4210_get_irq(38, 6)]);
+ sysbus_connect_irq(busdev, 15, s->irq_table[exynos4210_get_irq(38, 7)]);
+ sysbus_connect_irq(busdev, 16, s->irq_table[exynos4210_get_irq(39, 0)]);
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_GPIO2X_BASE_ADDR);
+
+ s->gpio3 = qdev_create(NULL, "exynos4210.gpio3");
+ qdev_init_nofail(s->gpio3);
+ busdev = sysbus_from_qdev(s->gpio3);
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_GPIO3_BASE_ADDR);
+
/*** I2C ***/
for (n = 0; n < EXYNOS4210_I2C_NUMBER; n++) {
uint32_t addr = EXYNOS4210_I2C_BASE_ADDR + EXYNOS4210_I2C_SHIFT * n;
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index 1205fb5..98b2dbd 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -76,6 +76,69 @@
#define EXYNOS4210_I2C_NUMBER 9
+/*
+ * Exynos4210 general purpose input/output (GPIO)
+ */
+
+/* GPIO part 1 port group numbers */
+#define EXYNOS4210_GPIO1_GPA0 0
+#define EXYNOS4210_GPIO1_GPA1 1
+#define EXYNOS4210_GPIO1_GPB 2
+#define EXYNOS4210_GPIO1_GPC0 3
+#define EXYNOS4210_GPIO1_GPC1 4
+#define EXYNOS4210_GPIO1_GPD0 5
+#define EXYNOS4210_GPIO1_GPD1 6
+#define EXYNOS4210_GPIO1_GPE0 7
+#define EXYNOS4210_GPIO1_GPE1 8
+#define EXYNOS4210_GPIO1_GPE2 9
+#define EXYNOS4210_GPIO1_GPE3 10
+#define EXYNOS4210_GPIO1_GPE4 11
+#define EXYNOS4210_GPIO1_GPF0 12
+#define EXYNOS4210_GPIO1_GPF1 13
+#define EXYNOS4210_GPIO1_GPF2 14
+#define EXYNOS4210_GPIO1_GPF3 15
+#define EXYNOS4210_GPIO1_ETC0 16
+#define EXYNOS4210_GPIO1_ETC1 17
+
+/* GPIO part 2 port group numbers */
+#define EXYNOS4210_GPIO2_GPJ0 0
+#define EXYNOS4210_GPIO2_GPJ1 1
+#define EXYNOS4210_GPIO2_GPK0 2
+#define EXYNOS4210_GPIO2_GPK1 3
+#define EXYNOS4210_GPIO2_GPK2 4
+#define EXYNOS4210_GPIO2_GPK3 5
+#define EXYNOS4210_GPIO2_GPL0 6
+#define EXYNOS4210_GPIO2_GPL1 7
+#define EXYNOS4210_GPIO2_GPL2 8
+#define EXYNOS4210_GPIO2_GPY0 9
+#define EXYNOS4210_GPIO2_GPY1 10
+#define EXYNOS4210_GPIO2_GPY2 11
+#define EXYNOS4210_GPIO2_GPY3 12
+#define EXYNOS4210_GPIO2_GPY4 13
+#define EXYNOS4210_GPIO2_GPY5 14
+#define EXYNOS4210_GPIO2_GPY6 15
+#define EXYNOS4210_GPIO2_ETC6 16
+/* GPIO part 2 X port groups numbers */
+#define EXYNOS4210_GPIO2X_GPX0 0
+#define EXYNOS4210_GPIO2X_GPX1 1
+#define EXYNOS4210_GPIO2X_GPX2 2
+#define EXYNOS4210_GPIO2X_GPX3 3
+
+/* GPIO part 3 port group numbers */
+#define EXYNOS4210_GPIO3_GPZ 0
+
+/* Get GPIO line number from GPIO port group name and group pin number */
+#define EXYNOS4210_GPIO_MAX_PIN_IN_PORT 8
+#define EXYNOS4210_GPIO1_LINE(group, pin) \
+ ((EXYNOS4210_GPIO1_##group * EXYNOS4210_GPIO_MAX_PIN_IN_PORT) + (pin))
+#define EXYNOS4210_GPIO2_LINE(group, pin) \
+ ((EXYNOS4210_GPIO2_##group * EXYNOS4210_GPIO_MAX_PIN_IN_PORT) + (pin))
+#define EXYNOS4210_GPIO2X_LINE(group, pin) \
+ ((EXYNOS4210_GPIO2X_##group * EXYNOS4210_GPIO_MAX_PIN_IN_PORT) + (pin))
+#define EXYNOS4210_GPIO3_LINE(group, pin) \
+ ((EXYNOS4210_GPIO3_##group * EXYNOS4210_GPIO_MAX_PIN_IN_PORT) + (pin))
+
+
typedef struct Exynos4210Irq {
qemu_irq int_combiner_irq[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ];
qemu_irq ext_combiner_irq[EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ];
@@ -98,6 +161,7 @@ typedef struct Exynos4210State {
MemoryRegion boot_secondary;
MemoryRegion bootreg_mem;
i2c_bus *i2c_if[EXYNOS4210_I2C_NUMBER];
+ DeviceState *gpio1, *gpio2, *gpio2x, *gpio3;
} Exynos4210State;
Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
diff --git a/hw/exynos4210_gpio.c b/hw/exynos4210_gpio.c
new file mode 100644
index 0000000..3e21541
--- /dev/null
+++ b/hw/exynos4210_gpio.c
@@ -0,0 +1,1117 @@
+/*
+ * Exynos4210 General Purpose Input/Output (GPIO) Emulation
+ *
+ * Copyright (C) 2012 Samsung Electronics Co Ltd.
+ * Maksim Kozlov, <m.kozlov@samsung.com>
+ * Igor Mitsyanko, <i.mitsyanko@samsung.com>
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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-common.h"
+#include "sysbus.h"
+#include "qdev.h"
+#include "irq.h"
+
+/* Debug messages configuration */
+#define EXYNOS4210_GPIO_DEBUG 0
+
+#if EXYNOS4210_GPIO_DEBUG == 0
+#define DPRINT_L1(fmt, args...) do { } while (0)
+#define DPRINT_L2(fmt, args...) do { } while (0)
+#define DPRINT_ERROR(fmt, args...) do { } while (0)
+#elif EXYNOS4210_GPIO_DEBUG == 1
+#define DPRINT_L1(fmt, args...) \
+ do {fprintf(stderr, "QEMU GPIO: "fmt, ## args); } while (0)
+#define DPRINT_L2(fmt, args...) do { } while (0)
+#define DPRINT_ERROR(fmt, args...) \
+ do {fprintf(stderr, "QEMU GPIO ERROR: "fmt, ## args); } while (0)
+#else
+#define DPRINT_L1(fmt, args...) \
+ do {fprintf(stderr, "QEMU GPIO: "fmt, ## args); } while (0)
+#define DPRINT_L2(fmt, args...) \
+ do {fprintf(stderr, "QEMU GPIO: "fmt, ## args); } while (0)
+#define DPRINT_ERROR(fmt, args...) \
+ do {fprintf(stderr, "QEMU GPIO ERROR: "fmt, ## args); } while (0)
+#endif
+
+
+#define TYPE_EXYNOS4210_GPIO "exynos4210.gpio"
+#define TYPE_EXYNOS4210_GPIO_1_2 "exynos4210.gpio-1_2"
+#define TYPE_EXYNOS4210_GPIO_2X "exynos4210.gpio2x"
+#define EXYNOS4210_GPIO(obj) \
+ OBJECT_CHECK(Exynos4GpioState, (obj), TYPE_EXYNOS4210_GPIO)
+#define EXYNOS4210_GPIO_1_2(obj) \
+ OBJECT_CHECK(Exynos4Gpio12State, (obj), TYPE_EXYNOS4210_GPIO_1_2)
+#define EXYNOS4210_GPIO2X(obj) \
+ OBJECT_CHECK(Exynos4Gpio2XState, (obj), TYPE_EXYNOS4210_GPIO_2X)
+
+#define GPIO1_REGS_MEM_SIZE 0x0b54
+#define GPIO2_REGS_MEM_SIZE 0x0b38
+#define GPIO2X_REGS_MEM_SIZE 0x0350
+#define GPIO3_REGS_MEM_SIZE 0x0018
+
+/* Port group registers offsets */
+#define GPIOCON 0x0000 /* Port Group Configuration Register */
+#define GPIODAT 0x0004 /* Port Group Data Register */
+#define GPIOPUD 0x0008 /* Port Group Pull-up/down Register */
+#define GPIODRV 0x000C /* Port Group Drive Strength Ctrl Reg */
+#define GPIOCONPDN 0x0010 /* Port Pwr Down Mode Config Reg */
+#define GPIOPUDPDN 0x0014 /* Port Pwr Down Mode Pullup/down Reg */
+
+/* GPIO pin functions */
+#define GPIOCON_IN 0x0 /* input pin */
+#define GPIOCON_OUT 0x1 /* output pin */
+#define GPIOCON_EXTINT 0xf /* external interrupt pin */
+
+/* External interrupts signaling methods */
+#define GPIO_INTCON_LOW 0x0 /* interrupt on low level */
+#define GPIO_INTCON_HIGH 0x1 /* interrupt on high level */
+#define GPIO_INTCON_FALL 0x2 /* interrupt on falling edge */
+#define GPIO_INTCON_RISE 0x3 /* interrupt on rising edge */
+#define GPIO_INTCON_FALLRISE 0x4 /* interrupt on both edges */
+
+/*
+ * Code assumes that each GPIO port group (GPA0, GPA1, e.t.c.)
+ * has 8 pins. When calculating GPIO line number to pass to function
+ * qdev_get_gpio_in() or qdev_connect_gpio_out(), you must assume the same,
+ * even though it's not true for real hardware.
+ */
+#define GPIO_MAX_PIN_IN_PORT 8
+#define GPIO_PULLUP_STATE 0x3
+#define GPIO_PORTGR_SIZE 0x20
+#define DIV_BY_PORTGR_SIZE(x) ((x) >> 5)
+#define MOD_PORTGR_SIZE(x) ((x) & (GPIO_PORTGR_SIZE - 1))
+#define GPIO_NORM_PORT_END 0x0200 /* norm ports mem area end */
+#define GPIO_ETCPORT_END 0x0240 /* etc ports mem area end */
+#define GPIO_EXTINT_CON_START 0x0700 /* extint con mem area start */
+#define GPIO_EXTINT_FLT_START 0x0800 /* extint filter mem area start */
+#define GPIO_EXTINT_MASK_START 0x0900 /* extint mask mem area start */
+#define GPIO_EXTINT_PEND_START 0x0A00 /* extint pend mem area start */
+#define GPIO_EXTINT_SERVICE 0x0B08 /* Current Service Register */
+#define GPIO_EXTINT_SERVICE_PEND 0x0B0C /* Current Service Pending Reg */
+#define GPIO_EXTINT_GRPFIXPRI 0x0B10 /* Ext Int Fixed Priority Ctrl */
+#define GPIO_EXTINT_FIXPRI_START 0x0B14 /* extint fixpri mem area start */
+
+/* GPIO part 1 specific defines */
+#define GPIO1_NORM_PORT_NUM 16
+#define GPIO1_ETC_PORT_NUM 2
+#define GPIO1_NUM_OF_PORTS (GPIO1_NORM_PORT_NUM + GPIO1_ETC_PORT_NUM)
+#define GPIO1_PORTINT_NUM GPIO1_NORM_PORT_NUM
+#define GPIO1_ETCPORT_START 0x0200
+#define GPIO1_EXTINT_CON_END 0x073C
+#define GPIO1_EXTINT_FLT_END 0x087C
+#define GPIO1_EXTINT_MASK_END 0x093C
+#define GPIO1_EXTINT_PEND_END 0x0A3C
+#define GPIO1_EXTINT_FIXPRI_END 0x0B50
+
+/* GPIO part 2 specific defines */
+#define GPIO2_NORM_PORT_NUM 16
+#define GPIO2_ETC_PORT_NUM 1
+#define GPIO2_NUM_OF_PORTS (GPIO2_NORM_PORT_NUM + GPIO2_ETC_PORT_NUM)
+#define GPIO2_PORTINT_NUM 9
+#define GPIO2_ETCPORT_START 0x0220
+#define GPIO2_EXTINT_CON_END 0x0724
+#define GPIO2_EXTINT_FLT_END 0x0848
+#define GPIO2_EXTINT_MASK_END 0x0924
+#define GPIO2_EXTINT_PEND_END 0x0A24
+#define GPIO2_EXTINT_FIXPRI_END 0x0B38
+
+/* GPIO part2 XPORT specific defines
+ * In Exynos documentation X ports are a part of GPIO part2, but we separate
+ * them to simplify implementation */
+#define GPIO2_X_PORT_NUM 4
+#define GPIO2_X_PORTINT_NUM GPIO2_X_PORT_NUM
+#define GPIO2_X_PORT_IRQ_NUM 17
+#define GPIO2_XPORT_END 0x0080
+#define GPIO2_WKPINT_CON_START 0x0200
+#define GPIO2_WKPINT_CON_END 0x0210
+#define GPIO2_WKPINT_FLT_START 0x0280
+#define GPIO2_WKPINT_FLT_END 0x02A0
+#define GPIO2_WKPINT_MASK_START 0x0300
+#define GPIO2_WKPINT_MASK_END 0x0310
+#define GPIO2_WKPINT_PEND_START 0x0340
+#define GPIO2_WKPINT_PEND_END 0x0350
+
+/* GPIO part 3 specific defines */
+#define GPIO3_NUM_OF_PORTS 1
+#define GPIO3_NORM_PORT_END 0x0020
+
+typedef enum {
+ GPIO_PART2X = 0,
+ GPIO_PART1,
+ GPIO_PART2,
+ GPIO_PART3,
+} Exynos4210GpioPart;
+
+typedef struct Exynos4PortGroup {
+ uint32_t con; /* configuration register */
+ uint32_t dat; /* data register */
+ uint32_t pud; /* pull-up/down register */
+ uint32_t drv; /* drive strength control register */
+ uint32_t conpdn; /* configuration register in power down mode */
+ uint32_t pudpdn; /* pull-up/down register in power down mode */
+
+ const char *name; /* port specific name */
+ const uint32_t def_con; /* default value for configuration register */
+ const uint32_t def_pud; /* default value for pull-up/down register */
+ const uint32_t def_drv; /* default value for drive strength control */
+} Exynos4PortGroup;
+
+static const VMStateDescription exynos4_gpio_portgroup_vmstate = {
+ .name = "exynos4210.gpio-port",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(con, Exynos4PortGroup),
+ VMSTATE_UINT32(dat, Exynos4PortGroup),
+ VMSTATE_UINT32(pud, Exynos4PortGroup),
+ VMSTATE_UINT32(drv, Exynos4PortGroup),
+ VMSTATE_UINT32(conpdn, Exynos4PortGroup),
+ VMSTATE_UINT32(pudpdn, Exynos4PortGroup),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+typedef struct Exynos4PortIntState {
+ uint32_t con; /* configuration register */
+ uint32_t fltcon[2]; /* filter configuraton registers 1,2 */
+ uint32_t mask; /* mask register */
+ uint32_t pend; /* interrupt pending register */
+ uint32_t fixpri; /* fixed priority control register */
+
+ const uint32_t def_mask; /* default value for mask register */
+ const uint8_t int_line_num; /* external interrupt line number */
+} Exynos4PortIntState;
+
+static const VMStateDescription exynos4_gpio_portint_vmstate = {
+ .name = "exynos4210.gpio-int",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(con, Exynos4PortIntState),
+ VMSTATE_UINT32(con, Exynos4PortIntState),
+ VMSTATE_UINT32_ARRAY(fltcon, Exynos4PortIntState, 2),
+ VMSTATE_UINT32(mask, Exynos4PortIntState),
+ VMSTATE_UINT32(pend, Exynos4PortIntState),
+ VMSTATE_UINT32(fixpri, Exynos4PortIntState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Exynos4PortGroup gpio1_ports[GPIO1_NUM_OF_PORTS] = {
+ { .name = "A0", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "A1", .def_con = 0, .def_pud = 0x0555, .def_drv = 0 },
+ { .name = "B", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "C0", .def_con = 0, .def_pud = 0x0155, .def_drv = 0 },
+ { .name = "C1", .def_con = 0, .def_pud = 0x0155, .def_drv = 0 },
+ { .name = "D0", .def_con = 0, .def_pud = 0x0055, .def_drv = 0 },
+ { .name = "D1", .def_con = 0, .def_pud = 0x0055, .def_drv = 0 },
+ { .name = "E0", .def_con = 0, .def_pud = 0x0155, .def_drv = 0 },
+ { .name = "E1", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "E2", .def_con = 0, .def_pud = 0x0555, .def_drv = 0 },
+ { .name = "E3", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "E4", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "F0", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "F1", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "F2", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "F3", .def_con = 0, .def_pud = 0x0555, .def_drv = 0 },
+ { .name = "ETC0", .def_con = 0, .def_pud = 0x0400, .def_drv = 0 },
+ { .name = "ETC1", .def_con = 0, .def_pud = 0x005, .def_drv = 0 },
+};
+
+/* All pins of all port groups of GPIO part1 share single GPIO IRQ line */
+static Exynos4PortIntState gpio1_ports_interrupts[GPIO1_PORTINT_NUM] = {
+ { .int_line_num = 1, .def_mask = 0x000000FF },
+ { .int_line_num = 2, .def_mask = 0x0000003F },
+ { .int_line_num = 3, .def_mask = 0x000000FF },
+ { .int_line_num = 4, .def_mask = 0x0000001F },
+ { .int_line_num = 5, .def_mask = 0x0000001F },
+ { .int_line_num = 6, .def_mask = 0x0000000F },
+ { .int_line_num = 7, .def_mask = 0x0000000F },
+ { .int_line_num = 8, .def_mask = 0x0000001F },
+ { .int_line_num = 9, .def_mask = 0x000000FF },
+ { .int_line_num = 10, .def_mask = 0x0000003F },
+ { .int_line_num = 11, .def_mask = 0x000000FF },
+ { .int_line_num = 12, .def_mask = 0x000000FF },
+ { .int_line_num = 13, .def_mask = 0x000000FF },
+ { .int_line_num = 14, .def_mask = 0x000000FF },
+ { .int_line_num = 15, .def_mask = 0x000000FF },
+ { .int_line_num = 16, .def_mask = 0x0000003F },
+};
+
+static Exynos4PortGroup gpio2_ports[GPIO2_NUM_OF_PORTS] = {
+ { .name = "J0", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "J1", .def_con = 0, .def_pud = 0x0155, .def_drv = 0 },
+ { .name = "K0", .def_con = 0, .def_pud = 0x1555, .def_drv = 0x002AAA },
+ { .name = "K1", .def_con = 0, .def_pud = 0x1555, .def_drv = 0 },
+ { .name = "K2", .def_con = 0, .def_pud = 0x1555, .def_drv = 0 },
+ { .name = "K3", .def_con = 0, .def_pud = 0x1555, .def_drv = 0 },
+ { .name = "L0", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "L1", .def_con = 0, .def_pud = 0x0015, .def_drv = 0 },
+ { .name = "L2", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "Y0", .def_con = 0x00225522, .def_pud = 0, .def_drv = 0x000AAA },
+ { .name = "Y1", .def_con = 0x00002222, .def_pud = 0, .def_drv = 0x0000AA },
+ { .name = "Y2", .def_con = 0x00255555, .def_pud = 0, .def_drv = 0x000AAA },
+ { .name = "Y3", .def_con = 0x22222222, .def_pud = 0, .def_drv = 0x00AAAA },
+ { .name = "Y4", .def_con = 0x22222222, .def_pud = 0, .def_drv = 0x00AAAA },
+ { .name = "Y5", .def_con = 0x22222222, .def_pud = 0, .def_drv = 0x00AAAA },
+ { .name = "Y6", .def_con = 0x22222222, .def_pud = 0, .def_drv = 0x00AAAA },
+ { .name = "ETC6", .def_con = 0, .def_pud = 0xC0C0, .def_drv = 0 },
+};
+
+/* All pins of all port groups of GPIO part2 share single interrupt line */
+static Exynos4PortIntState gpio2_ports_interrupts[GPIO2_PORTINT_NUM] = {
+ { .int_line_num = 21, .def_mask = 0x000000FF },
+ { .int_line_num = 22, .def_mask = 0x0000001F },
+ { .int_line_num = 23, .def_mask = 0x0000007F },
+ { .int_line_num = 24, .def_mask = 0x0000007F },
+ { .int_line_num = 25, .def_mask = 0x0000007F },
+ { .int_line_num = 26, .def_mask = 0x0000007F },
+ { .int_line_num = 27, .def_mask = 0x000000FF },
+ { .int_line_num = 28, .def_mask = 0x00000007 },
+ { .int_line_num = 29, .def_mask = 0x000000FF },
+};
+
+static Exynos4PortGroup gpio2x_ports[GPIO2_X_PORT_NUM] = {
+ { .name = "X0", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "X1", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "X2", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+ { .name = "X3", .def_con = 0, .def_pud = 0x5555, .def_drv = 0 },
+};
+
+/* Ports X0 and X1 have separate external irq lines for every pin.
+ * All pins of ports X2 and X3 share single external irq line */
+static Exynos4PortIntState gpio2x_ports_interrupts[GPIO2_X_PORTINT_NUM] = {
+ { .int_line_num = 0, .def_mask = 0x000000FF },
+ { .int_line_num = 1, .def_mask = 0x000000FF },
+ { .int_line_num = 2, .def_mask = 0x000000FF },
+ { .int_line_num = 3, .def_mask = 0x000000FF },
+};
+
+static Exynos4PortGroup gpio3_ports = {
+ .name = "Z", .def_con = 0, .def_pud = 0x1555, .def_drv = 0
+};
+
+typedef struct Exynos4GpioState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+ Exynos4210GpioPart part;
+ qemu_irq *out_cb; /* Callbacks on writing to GPIO line */
+ Exynos4PortGroup *ports;
+ Exynos4PortIntState *port_ints;
+ uint16_t num_of_ports;
+ uint16_t num_of_portints;
+} Exynos4GpioState;
+
+static const VMStateDescription exynos4_gpio_vmstate = {
+ .name = "exynos4210.gpio",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_VARRAY_POINTER_UINT16(ports, Exynos4GpioState,
+ num_of_ports, exynos4_gpio_portgroup_vmstate, Exynos4PortGroup),
+ VMSTATE_STRUCT_VARRAY_POINTER_UINT16(port_ints, Exynos4GpioState,
+ num_of_portints, exynos4_gpio_portint_vmstate, Exynos4PortIntState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+typedef struct Exynos4Gpio12State {
+ Exynos4GpioState gpio_common;
+ qemu_irq irq_gpio; /* GPIO interrupt request */
+ /* Group index and pin # of highest priority currently pended irq line */
+ uint32_t extint_serv;
+ uint32_t extint_serv_pend;
+ /* Index of highest priority interrupt group */
+ uint32_t extint_grpfixpri;
+} Exynos4Gpio12State;
+
+typedef struct Exynos4Gpio2XState {
+ Exynos4GpioState gpio_common;
+ qemu_irq ext_irq[GPIO2_X_PORT_IRQ_NUM];
+} Exynos4Gpio2XState;
+
+static inline void exynos4_gpio_reset_portgr(Exynos4PortGroup *group)
+{
+ group->con = group->def_con;
+ group->dat = 0;
+ group->pud = group->def_pud;
+ group->drv = group->def_drv;
+ group->conpdn = 0;
+ group->pudpdn = 0;
+}
+
+static inline void exynos4_gpio_reset_portint(Exynos4PortIntState *pint)
+{
+ pint->con = 0;
+ pint->mask = pint->def_mask;
+ pint->fltcon[0] = 0;
+ pint->fltcon[1] = 0;
+ pint->pend = 0;
+ pint->fixpri = 0;
+}
+
+static void exynos4_gpio_reset(DeviceState *dev)
+{
+ Exynos4GpioState *g = EXYNOS4210_GPIO(dev);
+ unsigned i;
+
+ DPRINT_L2("GPIO PART%u RESET\n", g->part);
+
+ switch (g->part) {
+ case GPIO_PART1: case GPIO_PART2:
+ qemu_irq_lower(EXYNOS4210_GPIO_1_2(g)->irq_gpio);
+ EXYNOS4210_GPIO_1_2(g)->extint_serv = 0;
+ EXYNOS4210_GPIO_1_2(g)->extint_grpfixpri = 0;
+ EXYNOS4210_GPIO_1_2(g)->extint_serv_pend = 0;
+ break;
+ case GPIO_PART2X:
+ for (i = 0; i < GPIO2_X_PORT_IRQ_NUM; i++) {
+ qemu_irq_lower(EXYNOS4210_GPIO2X(g)->ext_irq[i]);
+ }
+ break;
+ default:
+ break;
+ }
+
+ for (i = 0; i < g->num_of_portints; i++) {
+ exynos4_gpio_reset_portint(&g->port_ints[i]);
+ }
+
+ for (i = 0; i < g->num_of_ports; i++) {
+ exynos4_gpio_reset_portgr(&g->ports[i]);
+ }
+}
+
+static uint32_t
+exynos4_gpio_portgr_read(Exynos4PortGroup *group, target_phys_addr_t off)
+{
+ DPRINT_L2("Port group GP%s read off 0x%x\n", group->name, off);
+
+ switch (off) {
+ case GPIOCON:
+ return group->con;
+ case GPIODAT:
+ return group->dat & 0xff;
+ case GPIOPUD:
+ return group->pud;
+ case GPIODRV:
+ return group->drv;
+ case GPIOCONPDN:
+ return group->conpdn;
+ case GPIOPUDPDN:
+ return group->pudpdn;
+ default:
+ DPRINT_ERROR("Port group GP%s bad read off 0x%x\n", group->name, off);
+ return 0xBAADBAAD;
+ }
+}
+
+static uint32_t
+exynos4_etc_portgroup_read(Exynos4PortGroup *group, target_phys_addr_t off)
+{
+ DPRINT_L1("Port group %s read off 0x%x\n", group->name, off);
+
+ switch (off) {
+ case GPIOPUD:
+ return group->pud;
+ case GPIODRV:
+ return group->drv;
+ default:
+ DPRINT_ERROR("Port group %s bad read off 0x%x\n", group->name, off);
+ return 0xBAADBAAD;
+ }
+}
+
+static void exynos4_etc_portgroup_write(Exynos4PortGroup *group,
+ target_phys_addr_t off, uint32_t value)
+{
+ DPRINT_L1("Port group %s write: off 0x%x = %u(0x%x)\n",
+ group->name, off, value, value);
+
+ switch (off) {
+ case GPIOPUD:
+ group->pud = value;
+ break;
+ case GPIODRV:
+ group->drv = value;
+ break;
+ default:
+ DPRINT_ERROR("Port group %s bad write: off 0x%x = %u(0x%x)\n",
+ group->name, off, value, value);
+ break;
+ }
+}
+
+/* Returns index of currently pended external interrupt line with highest
+ * priority 1..MAX_INDEX for GPIO parts 1 and 2 */
+static unsigned int
+gpio_group_get_highest_prio(Exynos4GpioState *g)
+{
+ Exynos4Gpio12State *g12 = EXYNOS4210_GPIO_1_2(g);
+ unsigned i;
+ const unsigned num_of_prio = g->num_of_portints;
+ uint8_t highest_prio = g12->extint_grpfixpri;
+
+ if (highest_prio == 0) {
+ /* Zero extint_grpfixpri is equal to extint_grpfixpri == 1 */
+ highest_prio = 1;
+ }
+
+ /* Corresponding group index is less then EXTINT index by one */
+ highest_prio--;
+ for (i = 0; i < num_of_prio; i++) {
+ if (g->port_ints[highest_prio].pend &
+ ~g->port_ints[highest_prio].mask) {
+ return highest_prio + 1;
+ }
+ if (++highest_prio >= num_of_prio) {
+ highest_prio = 0;
+ }
+ }
+
+ return 0;
+}
+
+/* Returns line number of highest pended external irq within portgroup */
+static unsigned int gpio_get_highest_intnum(Exynos4GpioState *g, unsigned group)
+{
+ uint8_t highest_prio = g->port_ints[group].fixpri;
+ uint8_t pend = g->port_ints[group].pend;
+ unsigned i;
+
+ for (i = 0; i < GPIO_MAX_PIN_IN_PORT; i++) {
+ if (pend & (1 << highest_prio)) {
+ return highest_prio;
+ }
+ if (++highest_prio >= GPIO_MAX_PIN_IN_PORT) {
+ highest_prio = 0;
+ }
+ }
+
+ return 0;
+}
+
+/* Clear GPIO IRQ if none of gpio interrupt lines are pended */
+static void exynos4_gpioirq_update(Exynos4GpioState *g)
+{
+ Exynos4Gpio12State *g12 = EXYNOS4210_GPIO_1_2(g);
+ unsigned pend_prio = gpio_group_get_highest_prio(g);
+
+ if (pend_prio == 0) {
+ DPRINT_L2("GPIO part %u interrupt cleared\n", g->part);
+ g12->extint_serv = g12->extint_serv_pend = 0;
+ qemu_irq_lower(g12->irq_gpio);
+ } else if (pend_prio != ((g12->extint_serv >> 3) & 0x1f)) {
+ g12->extint_serv = (pend_prio << 3) |
+ gpio_get_highest_intnum(g, pend_prio - 1);
+ g12->extint_serv_pend = g->port_ints[pend_prio - 1].pend;
+ }
+}
+
+static void exynos4_gpio_portgr_write(Exynos4GpioState *g, int idx,
+ unsigned int off, uint32_t value)
+{
+ Exynos4PortGroup *group = &g->ports[idx];
+ unsigned pin;
+ uint32_t diff, old_con, new_dat;
+
+ DPRINT_L1("Port group GP%s write: off 0x%x = %u(0x%x)\n",
+ group->name, off, value, value);
+
+ switch (off) {
+ case GPIOCON:
+ old_con = group->con;
+ group->con = value;
+ for (pin = 0; pin < GPIO_MAX_PIN_IN_PORT; pin++) {
+ if (((value >> pin * 4) & 0xf) != ((old_con >> pin * 4) & 0xf)) {
+ qemu_irq_raise(g->out_cb[idx * GPIO_MAX_PIN_IN_PORT + pin]);
+ }
+ }
+ break;
+ case GPIODAT:
+ new_dat = group->dat;
+ value &= (1 << GPIO_MAX_PIN_IN_PORT) - 1;
+ for (pin = 0; pin < GPIO_MAX_PIN_IN_PORT; pin++) {
+ if (((group->con >> pin * 4) & 0xf) == GPIOCON_OUT) {
+ new_dat = (new_dat & ~(1 << pin)) | (value & (1 << pin));
+ }
+ }
+ diff = group->dat ^ new_dat;
+ group->dat = new_dat & ((1 << GPIO_MAX_PIN_IN_PORT) - 1);
+ while ((pin = ffs(diff))) {
+ pin--;
+ DPRINT_L2("Port group GP%s pin #%u write callback %s raised\n",
+ group->name, pin, (g->out_cb[idx *
+ GPIO_MAX_PIN_IN_PORT + pin] ? "" : "wasn't"));
+ qemu_set_irq(g->out_cb[idx * GPIO_MAX_PIN_IN_PORT + pin],
+ (group->dat & (1 << pin)));
+ diff &= ~(1 << pin);
+ }
+ break;
+ case GPIOPUD:
+ for (pin = 0; pin < GPIO_MAX_PIN_IN_PORT; pin++) {
+ if (((value >> 2 * pin) & 0x3) == GPIO_PULLUP_STATE &&
+ ((group->pud >> 2 * pin) & 0x3) != GPIO_PULLUP_STATE) {
+ group->dat |= 1 << pin;
+ }
+ }
+ group->pud = value;
+ break;
+ case GPIODRV:
+ group->drv = value;
+ break;
+ case GPIOCONPDN:
+ group->conpdn = value;
+ break;
+ case GPIOPUDPDN:
+ group->pudpdn = value;
+ break;
+ default:
+ DPRINT_ERROR("Port group GP%s bad write: offset 0x%x = %u(0x%x)\n",
+ group->name, off, value, value);
+ break;
+ }
+}
+
+static void exynos4_gpio_set_cb(void *opaque, int line, int level)
+{
+ Exynos4GpioState *g = (Exynos4GpioState *)opaque;
+ const unsigned group_num = line >> 3;
+ const unsigned pin = line & (GPIO_MAX_PIN_IN_PORT - 1);
+ bool irq_is_triggered = false;
+ const uint32_t dat_prev = g->ports[group_num].dat & (1 << pin);
+ const unsigned pin_func = (g->ports[group_num].con >> pin * 4) & 0xf;
+
+ /* Check that corresponding pin is in input state */
+ if (pin_func != GPIOCON_EXTINT && pin_func != GPIOCON_IN) {
+ return;
+ }
+
+ DPRINT_L1("Input pin GPIO%s_PIN%u %s by external device\n",
+ g->ports[group_num].name, pin, (level ? "set" : "cleared"));
+ /* Set new value on corresponding gpio pin */
+ (level) ? (g->ports[group_num].dat |= (1 << pin)) :
+ (g->ports[group_num].dat &= ~(1 << pin));
+
+ /* Check that external interrupt function is active for this pin */
+ if (pin_func != GPIOCON_EXTINT || group_num >= g->num_of_portints) {
+ return;
+ }
+
+ /* Do nothing if corresponding interrupt line is masked or already pended */
+ if ((g->port_ints[group_num].mask & (1 << pin)) ||
+ (g->port_ints[group_num].pend & (1 << pin))) {
+ return;
+ }
+
+ /* Get interrupt line signaling method */
+ switch ((g->port_ints[group_num].con >> (pin * 4)) & 7) {
+ case GPIO_INTCON_LOW:
+ irq_is_triggered = !level;
+ break;
+ case GPIO_INTCON_HIGH:
+ irq_is_triggered = !!level;
+ break;
+ case GPIO_INTCON_FALL:
+ irq_is_triggered = dat_prev && !(g->ports[group_num].dat & (1 << pin));
+ break;
+ case GPIO_INTCON_RISE:
+ irq_is_triggered = !dat_prev && (g->ports[group_num].dat & (1 << pin));
+ break;
+ case GPIO_INTCON_FALLRISE:
+ irq_is_triggered =
+ (dat_prev && !(g->ports[group_num].dat & (1 << pin))) ||
+ (!dat_prev && (g->ports[group_num].dat & (1 << pin)));
+ break;
+ default:
+ DPRINT_ERROR("GPIO PART%u: unknown triggering method of EXT_IRQ_%u\n",
+ g->part, g->port_ints[group_num].int_line_num);
+ break;
+ }
+
+ if (irq_is_triggered) {
+ g->port_ints[group_num].pend |= 1 << pin;
+ if (g->part == GPIO_PART2X) {
+ unsigned irq = group_num * GPIO_MAX_PIN_IN_PORT + pin;
+ DPRINT_L1("IRQ_EINT%u raised\n", irq);
+ if (irq >= GPIO2_X_PORT_IRQ_NUM) {
+ irq = GPIO2_X_PORT_IRQ_NUM - 1;
+ }
+ qemu_irq_raise(EXYNOS4210_GPIO2X(g)->ext_irq[irq]);
+ } else {
+ Exynos4Gpio12State *g12 = EXYNOS4210_GPIO_1_2(g);
+ DPRINT_L1("GPIO_INT%u[PIN%u] raised and GPIO_P%u_IRQ raised\n",
+ g->port_ints[group_num].int_line_num, pin, g->part);
+
+ if ((group_num + 1) == gpio_group_get_highest_prio(g)) {
+ g12->extint_serv = ((group_num + 1) << 3) |
+ gpio_get_highest_intnum(g, group_num);
+ g12->extint_serv_pend = g->port_ints[group_num].pend;
+ }
+ qemu_irq_raise(g12->irq_gpio);
+ }
+ }
+}
+
+static uint64_t exynos4_gpio_read(void *opaque, target_phys_addr_t off,
+ unsigned size)
+{
+ Exynos4GpioState *g = (Exynos4GpioState *)opaque;
+ unsigned port_end, extint_con_start, extint_con_end;
+ unsigned extint_flt_start, extint_flt_end;
+ unsigned extint_mask_start, extint_mask_end;
+ unsigned extint_pend_start, extint_pend_end;
+ unsigned etcp_start_addr, etcp_start_idx, extint_pri_end;
+ unsigned idx;
+
+ DPRINT_L2("GPIO%u read off 0x%x\n", g->part, (uint32_t)off);
+
+ switch (g->part) {
+ case GPIO_PART2X:
+ port_end = GPIO2_XPORT_END;
+ extint_con_start = GPIO2_WKPINT_CON_START;
+ extint_con_end = GPIO2_WKPINT_CON_END;
+ extint_flt_start = GPIO2_WKPINT_FLT_START;
+ extint_flt_end = GPIO2_WKPINT_FLT_END;
+ extint_mask_start = GPIO2_WKPINT_MASK_START;
+ extint_mask_end = GPIO2_WKPINT_MASK_END;
+ extint_pend_start = GPIO2_WKPINT_PEND_START;
+ extint_pend_end = GPIO2_WKPINT_PEND_END;
+ etcp_start_addr = etcp_start_idx = extint_pri_end = 0;
+ break;
+ case GPIO_PART1: default:
+ port_end = GPIO_NORM_PORT_END;
+ extint_con_start = GPIO_EXTINT_CON_START;
+ extint_con_end = GPIO1_EXTINT_CON_END;
+ extint_flt_start = GPIO_EXTINT_FLT_START;
+ extint_flt_end = GPIO1_EXTINT_FLT_END;
+ extint_mask_start = GPIO_EXTINT_MASK_START;
+ extint_mask_end = GPIO1_EXTINT_MASK_END;
+ extint_pend_start = GPIO_EXTINT_PEND_START;
+ extint_pend_end = GPIO1_EXTINT_PEND_END;
+ etcp_start_addr = GPIO1_ETCPORT_START;
+ etcp_start_idx = GPIO1_NORM_PORT_NUM;
+ extint_pri_end = GPIO1_EXTINT_FIXPRI_END;
+ break;
+ case GPIO_PART2:
+ port_end = GPIO_NORM_PORT_END;
+ extint_con_start = GPIO_EXTINT_CON_START;
+ extint_con_end = GPIO2_EXTINT_CON_END;
+ extint_flt_start = GPIO_EXTINT_FLT_START;
+ extint_flt_end = GPIO2_EXTINT_FLT_END;
+ extint_mask_start = GPIO_EXTINT_MASK_START;
+ extint_mask_end = GPIO2_EXTINT_MASK_END;
+ extint_pend_start = GPIO_EXTINT_PEND_START;
+ extint_pend_end = GPIO2_EXTINT_PEND_END;
+ etcp_start_addr = GPIO2_ETCPORT_START;
+ etcp_start_idx = GPIO2_NORM_PORT_NUM;
+ extint_pri_end = GPIO2_EXTINT_FIXPRI_END;
+ break;
+ case GPIO_PART3:
+ if (off < GPIO3_NORM_PORT_END) {
+ idx = DIV_BY_PORTGR_SIZE(off);
+ return exynos4_gpio_portgr_read(&g->ports[idx],
+ MOD_PORTGR_SIZE(off));
+ }
+ DPRINT_ERROR("GPIO part 3 bad read off 0x%x\n", off);
+ return 0xBAADBAAD;
+ }
+
+ if (off < port_end) {
+ idx = DIV_BY_PORTGR_SIZE(off);
+ return exynos4_gpio_portgr_read(&g->ports[idx], MOD_PORTGR_SIZE(off));
+ } else if (off >= extint_mask_start && off < extint_mask_end) {
+ idx = (off - extint_mask_start) >> 2;
+ DPRINT_L1("GPIO%u EXTINT%u_MASK register read\n", g->part,
+ g->port_ints[idx].int_line_num);
+ return g->port_ints[idx].mask;
+ } else if (off >= extint_pend_start && off < extint_pend_end) {
+ idx = (off - extint_pend_start) >> 2;
+ DPRINT_L1("GPIO%u EXTINT%u_PEND register read\n", g->part,
+ g->port_ints[idx].int_line_num);
+ return g->port_ints[idx].pend;
+ } else if (off >= extint_con_start && off < extint_con_end) {
+ idx = (off - extint_con_start) >> 2;
+ DPRINT_L1("GPIO%u EXTINT%u_CON register read\n", g->part,
+ g->port_ints[idx].int_line_num);
+ return g->port_ints[idx].con;
+ } else if (off >= extint_flt_start && off < extint_flt_end) {
+ unsigned i = ((off - extint_flt_start) >> 2) & 1;
+ idx = (off - extint_flt_start) >> 3;
+ DPRINT_L1("GPIO%u EXTINT%u_FLTCON%u register read\n", g->part,
+ g->port_ints[idx].int_line_num, i);
+ return g->port_ints[idx].fltcon[i];
+ } else if (g->part == GPIO_PART2X) {
+ /* GPIO group X has no more registers */
+ DPRINT_ERROR("GPIO group X bad read off 0x%x\n", (uint32_t)off);
+ return 0xBAADBAAD;
+ } else if (off == GPIO_EXTINT_SERVICE) {
+ DPRINT_L1("GPIO%u EXT_INT_SERVICE register read\n", g->part);
+ return EXYNOS4210_GPIO_1_2(g)->extint_serv;
+ } else if (off == GPIO_EXTINT_SERVICE_PEND) {
+ DPRINT_L1("GPIO%u EXT_INT_SERVICE_PEND register read\n", g->part);
+ return EXYNOS4210_GPIO_1_2(g)->extint_serv_pend;
+ } else if (off == GPIO_EXTINT_GRPFIXPRI) {
+ DPRINT_L1("GPIO%u EXT_INT_GRPFIXPRI register read\n", g->part);
+ return EXYNOS4210_GPIO_1_2(g)->extint_grpfixpri;
+ } else if (off >= GPIO_EXTINT_FIXPRI_START && off < extint_pri_end) {
+ idx = (off - GPIO_EXTINT_FIXPRI_START) >> 2;
+ DPRINT_L1("GPIO%u EXTINT%u_FIXPRI register read\n", g->part,
+ g->port_ints[idx].int_line_num);
+ return g->port_ints[idx].fixpri;
+ } else if (off >= etcp_start_addr && off < GPIO_ETCPORT_END) {
+ idx = DIV_BY_PORTGR_SIZE(off - etcp_start_addr) +
+ etcp_start_idx;
+ return exynos4_etc_portgroup_read(&g->ports[idx],
+ MOD_PORTGR_SIZE(off - etcp_start_addr));
+ }
+
+ DPRINT_ERROR("GPIO_P%u bad read off 0x%x\n", g->part, (uint32_t)off);
+ return 0xBAADBAAD;
+}
+
+static void exynos4_gpio_write(void *opaque, target_phys_addr_t off,
+ uint64_t value, unsigned size)
+{
+ Exynos4GpioState *g = (Exynos4GpioState *)opaque;
+ unsigned int port_end, extint_con_start, extint_con_end;
+ unsigned int extint_flt_start, extint_flt_end;
+ unsigned int extint_mask_start, extint_mask_end;
+ unsigned int extint_pend_start, extint_pend_end;
+ unsigned int etcp_start_addr, etcp_start_idx, extint_pri_end;
+ unsigned idx;
+
+ DPRINT_L2("GPIO%u write off 0x%x = %u(0x%x)\n", g->part,
+ (uint32_t)off, (uint32_t)value, (uint32_t)value);
+
+ switch (g->part) {
+ case GPIO_PART2X:
+ port_end = GPIO2_XPORT_END;
+ extint_con_start = GPIO2_WKPINT_CON_START;
+ extint_con_end = GPIO2_WKPINT_CON_END;
+ extint_flt_start = GPIO2_WKPINT_FLT_START;
+ extint_flt_end = GPIO2_WKPINT_FLT_END;
+ extint_mask_start = GPIO2_WKPINT_MASK_START;
+ extint_mask_end = GPIO2_WKPINT_MASK_END;
+ extint_pend_start = GPIO2_WKPINT_PEND_START;
+ extint_pend_end = GPIO2_WKPINT_PEND_END;
+ etcp_start_addr = etcp_start_idx = extint_pri_end = 0;
+ break;
+ case GPIO_PART1: default:
+ port_end = GPIO_NORM_PORT_END;
+ extint_con_start = GPIO_EXTINT_CON_START;
+ extint_con_end = GPIO1_EXTINT_CON_END;
+ extint_flt_start = GPIO_EXTINT_FLT_START;
+ extint_flt_end = GPIO1_EXTINT_FLT_END;
+ extint_mask_start = GPIO_EXTINT_MASK_START;
+ extint_mask_end = GPIO1_EXTINT_MASK_END;
+ extint_pend_start = GPIO_EXTINT_PEND_START;
+ extint_pend_end = GPIO1_EXTINT_PEND_END;
+ etcp_start_addr = GPIO1_ETCPORT_START;
+ etcp_start_idx = GPIO1_NORM_PORT_NUM;
+ extint_pri_end = GPIO1_EXTINT_FIXPRI_END;
+ break;
+ case GPIO_PART2:
+ port_end = GPIO_NORM_PORT_END;
+ extint_con_start = GPIO_EXTINT_CON_START;
+ extint_con_end = GPIO2_EXTINT_CON_END;
+ extint_flt_start = GPIO_EXTINT_FLT_START;
+ extint_flt_end = GPIO2_EXTINT_FLT_END;
+ extint_mask_start = GPIO_EXTINT_MASK_START;
+ extint_mask_end = GPIO2_EXTINT_MASK_END;
+ extint_pend_start = GPIO_EXTINT_PEND_START;
+ extint_pend_end = GPIO2_EXTINT_PEND_END;
+ etcp_start_addr = GPIO2_ETCPORT_START;
+ etcp_start_idx = GPIO2_NORM_PORT_NUM;
+ extint_pri_end = GPIO2_EXTINT_FIXPRI_END;
+ break;
+ case GPIO_PART3:
+ if (off < GPIO3_NORM_PORT_END) {
+ idx = DIV_BY_PORTGR_SIZE(off);
+ exynos4_gpio_portgr_write(g, idx, MOD_PORTGR_SIZE(off), value);
+ } else {
+ DPRINT_ERROR("GPIO3 bad write off 0x%x = %u(0x%x)\n",
+ (uint32_t)off, (uint32_t)value, (uint32_t)value);
+ }
+ return;
+ }
+
+ if (off < port_end) {
+ idx = DIV_BY_PORTGR_SIZE(off);
+ exynos4_gpio_portgr_write(g, idx, MOD_PORTGR_SIZE(off), value);
+ } else if (off >= extint_mask_start && off < extint_mask_end) {
+ idx = (off - extint_mask_start) >> 2;
+ DPRINT_L1("GPIO%u EXTINT%u_MASK register write = %u(0x%x)\n", g->part,
+ g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
+ g->port_ints[idx].mask = value;
+ } else if (off >= extint_pend_start && off < extint_pend_end) {
+ idx = (off - extint_pend_start) >> 2;
+ DPRINT_L1("GPIO%u EXTINT%u_PEND register write = %u(0x%x)\n", g->part,
+ g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
+ if (g->part == GPIO_PART2X) {
+ unsigned i, irq_n;
+ Exynos4Gpio2XState *g2 = EXYNOS4210_GPIO2X(g);
+ for (i = 0; i < GPIO_MAX_PIN_IN_PORT; i++) {
+ if ((g->port_ints[idx].pend & (1 << i)) && (value & (1 << i))) {
+ g->port_ints[idx].pend &= ~(1 << i);
+ irq_n = idx * GPIO_MAX_PIN_IN_PORT + i;
+ if (irq_n >= GPIO2_X_PORT_IRQ_NUM) {
+ irq_n = GPIO2_X_PORT_IRQ_NUM - 1;
+ }
+ qemu_irq_lower(g2->ext_irq[irq_n]);
+ }
+ }
+ } else {
+ g->port_ints[idx].pend &= ~value;
+ exynos4_gpioirq_update(g);
+ }
+ } else if (off >= extint_con_start && off < extint_con_end) {
+ idx = (off - extint_con_start) >> 2;
+ DPRINT_L1("GPIO%u EXTINT%u_CON register write = %u(0x%x)\n", g->part,
+ g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
+ g->port_ints[idx].con = value;
+ } else if (off >= extint_flt_start && off < extint_flt_end) {
+ unsigned i = ((off - extint_flt_start) >> 2) & 1;
+ idx = (off - extint_flt_start) >> 3;
+ DPRINT_L1("GPIO%u EXTINT%u_FLTCON%u reg write = %u(0x%x)\n", g->part,
+ g->port_ints[idx].int_line_num, i, (uint32_t)value, (uint32_t)value);
+ g->port_ints[idx].fltcon[i] = value;
+ } else if (g->part == GPIO_PART2X) {
+ DPRINT_ERROR("GPIO2 group X bad write off 0x%x = %u(0x%x)\n",
+ (uint32_t)off, (uint32_t)value, (uint32_t)value);
+ return;
+ } else if (off == GPIO_EXTINT_SERVICE) {
+ DPRINT_L1("GPIO%u EXT_INT_SERVICE register write = %u(0x%x)\n",
+ g->part, (uint32_t)value, (uint32_t)value);
+ EXYNOS4210_GPIO_1_2(g)->extint_serv = value;
+ } else if (off == GPIO_EXTINT_SERVICE_PEND) {
+ DPRINT_L1("GPIO%u EXT_INT_SERVICE_PEND register write = %u(0x%x)\n",
+ g->part, (uint32_t)value, (uint32_t)value);
+ EXYNOS4210_GPIO_1_2(g)->extint_serv_pend = value;
+ } else if (off == GPIO_EXTINT_GRPFIXPRI) {
+ DPRINT_L1("GPIO%u EXT_INT_GRPFIXPRI register write = %u(0x%x)\n",
+ g->part, (uint32_t)value, (uint32_t)value);
+ EXYNOS4210_GPIO_1_2(g)->extint_grpfixpri = value;
+ } else if (off >= GPIO_EXTINT_FIXPRI_START && off < extint_pri_end) {
+ idx = (off - GPIO_EXTINT_FIXPRI_START) >> 2;
+ DPRINT_L1("GPIO%u EXTINT%u_FIXPRI register write = %u(0x%x)\n", g->part,
+ g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
+ g->port_ints[idx].fixpri = value;
+ } else if (off >= etcp_start_addr && off < GPIO_ETCPORT_END) {
+ idx = etcp_start_idx + DIV_BY_PORTGR_SIZE(off - etcp_start_addr);
+ exynos4_etc_portgroup_write(&g->ports[idx],
+ MOD_PORTGR_SIZE(off - etcp_start_addr), value);
+ } else {
+ DPRINT_ERROR("GPIO%u bad write off 0x%x = %u(0x%x)\n", g->part,
+ (uint32_t)off, (uint32_t)value, (uint32_t)value);
+ }
+}
+
+static const MemoryRegionOps exynos4_gpio_mmio_ops = {
+ .read = exynos4_gpio_read,
+ .write = exynos4_gpio_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription exynos4_gpio_1_2_vmstate = {
+ .name = "exynos4210.gpio-1_2",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(gpio_common, Exynos4Gpio12State, 1,
+ exynos4_gpio_vmstate, Exynos4GpioState),
+ VMSTATE_UINT32(extint_serv, Exynos4Gpio12State),
+ VMSTATE_UINT32(extint_serv_pend, Exynos4Gpio12State),
+ VMSTATE_UINT32(extint_grpfixpri, Exynos4Gpio12State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void exynos4_gpio1_init(Object *obj)
+{
+ Exynos4GpioState *g = EXYNOS4210_GPIO(obj);
+
+ g->part = GPIO_PART1;
+ g->ports = gpio1_ports;
+ g->port_ints = gpio1_ports_interrupts;
+ g->num_of_ports = sizeof(gpio1_ports) / sizeof(Exynos4PortGroup);
+ g->num_of_portints =
+ sizeof(gpio1_ports_interrupts) / sizeof(Exynos4PortIntState);
+}
+
+static void exynos4_gpio2_init(Object *obj)
+{
+ Exynos4GpioState *g = EXYNOS4210_GPIO(obj);
+
+ g->part = GPIO_PART2;
+ g->ports = gpio2_ports;
+ g->port_ints = gpio2_ports_interrupts;
+ g->num_of_ports = sizeof(gpio2_ports) / sizeof(Exynos4PortGroup);
+ g->num_of_portints =
+ sizeof(gpio2_ports_interrupts) / sizeof(Exynos4PortIntState);
+}
+
+static void exynos4_gpio2x_init(Object *obj)
+{
+ Exynos4GpioState *g = EXYNOS4210_GPIO(obj);
+
+ g->part = GPIO_PART2X;
+ g->ports = gpio2x_ports;
+ g->port_ints = gpio2x_ports_interrupts;
+ g->num_of_ports = sizeof(gpio2x_ports) / sizeof(Exynos4PortGroup);
+ g->num_of_portints =
+ sizeof(gpio2x_ports_interrupts) / sizeof(Exynos4PortIntState);
+}
+
+static void exynos4_gpio3_init(Object *obj)
+{
+ Exynos4GpioState *g = EXYNOS4210_GPIO(obj);
+
+ g->part = GPIO_PART3;
+ g->ports = &gpio3_ports;
+ g->num_of_ports = sizeof(gpio3_ports) / sizeof(Exynos4PortGroup);
+ g->num_of_portints = 0;
+}
+
+static int exynos4_gpio_realize(SysBusDevice *busdev)
+{
+ Exynos4GpioState *g = EXYNOS4210_GPIO(busdev);
+ unsigned int mem_size, i;
+ const char *iomem_name;
+
+ switch (g->part) {
+ case GPIO_PART1:
+ iomem_name = "exynos4210.gpio1";
+ mem_size = GPIO1_REGS_MEM_SIZE;
+ sysbus_init_irq(busdev, &EXYNOS4210_GPIO_1_2(busdev)->irq_gpio);
+ break;
+ case GPIO_PART2:
+ iomem_name = "exynos4210.gpio2";
+ mem_size = GPIO2_REGS_MEM_SIZE;
+ sysbus_init_irq(busdev, &EXYNOS4210_GPIO_1_2(busdev)->irq_gpio);
+ break;
+ case GPIO_PART2X:
+ iomem_name = "exynos4210.gpio2x";
+ mem_size = GPIO2X_REGS_MEM_SIZE;
+ for (i = 0; i < GPIO2_X_PORT_IRQ_NUM; i++) {
+ Exynos4Gpio2XState *g2x = EXYNOS4210_GPIO2X(busdev);
+ sysbus_init_irq(busdev, &g2x->ext_irq[i]);
+ }
+ break;
+ case GPIO_PART3:
+ iomem_name = "exynos4210.gpio3";
+ mem_size = GPIO3_REGS_MEM_SIZE;
+ break;
+ default:
+ hw_error("QEMU GPIO INIT ERROR: unknown GPIO part\n");
+ }
+
+ g->out_cb = g_new0(qemu_irq, g->num_of_ports * GPIO_MAX_PIN_IN_PORT);
+ qdev_init_gpio_in(DEVICE(busdev), exynos4_gpio_set_cb,
+ g->num_of_ports * GPIO_MAX_PIN_IN_PORT);
+ qdev_init_gpio_out(DEVICE(busdev), g->out_cb,
+ g->num_of_ports * GPIO_MAX_PIN_IN_PORT);
+ memory_region_init_io(&g->iomem, &exynos4_gpio_mmio_ops, g,
+ iomem_name, mem_size);
+ sysbus_init_mmio(busdev, &g->iomem);
+ return 0;
+}
+
+static void exynos4_gpio_finalize(Object *obj)
+{
+ Exynos4GpioState *g = EXYNOS4210_GPIO(obj);
+
+ if (g->out_cb) {
+ g_free(g->out_cb);
+ g->out_cb = NULL;
+ }
+}
+
+static void exynos4_gpio_class_init(ObjectClass *class, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(class);
+ SysBusDeviceClass *sbd = SYS_BUS_DEVICE_CLASS(class);
+ dc->reset = exynos4_gpio_reset;
+ dc->vmsd = &exynos4_gpio_vmstate;
+ sbd->init = exynos4_gpio_realize;
+}
+
+static void exynos4_gpio_1_2_class_init(ObjectClass *class, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(class);
+
+ dc->vmsd = &exynos4_gpio_1_2_vmstate;
+}
+
+static TypeInfo exynos4_gpio_type_info = {
+ .name = TYPE_EXYNOS4210_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4GpioState),
+ .class_init = exynos4_gpio_class_init,
+ .instance_finalize = exynos4_gpio_finalize,
+ .abstract = true
+};
+
+static TypeInfo exynos4_gpio_1_2_type_info = {
+ .name = TYPE_EXYNOS4210_GPIO_1_2,
+ .parent = TYPE_EXYNOS4210_GPIO,
+ .instance_size = sizeof(Exynos4Gpio12State),
+ .class_init = exynos4_gpio_1_2_class_init,
+ .abstract = true
+};
+
+static TypeInfo exynos4_gpio1_type_info = {
+ .name = "exynos4210.gpio1",
+ .parent = TYPE_EXYNOS4210_GPIO_1_2,
+ .instance_init = exynos4_gpio1_init,
+};
+
+static TypeInfo exynos4_gpio2_type_info = {
+ .name = "exynos4210.gpio2",
+ .parent = TYPE_EXYNOS4210_GPIO_1_2,
+ .instance_init = exynos4_gpio2_init,
+};
+
+static TypeInfo exynos4_gpio2x_type_info = {
+ .name = TYPE_EXYNOS4210_GPIO_2X,
+ .parent = TYPE_EXYNOS4210_GPIO,
+ .instance_size = sizeof(Exynos4Gpio2XState),
+ .instance_init = exynos4_gpio2x_init,
+};
+
+static TypeInfo exynos4_gpio3_type_info = {
+ .name = "exynos4210.gpio3",
+ .parent = TYPE_EXYNOS4210_GPIO,
+ .instance_init = exynos4_gpio3_init,
+};
+
+static void exynos4_gpio_register_types(void)
+{
+ type_register_static(&exynos4_gpio_type_info);
+ type_register_static(&exynos4_gpio_1_2_type_info);
+ type_register_static(&exynos4_gpio1_type_info);
+ type_register_static(&exynos4_gpio2_type_info);
+ type_register_static(&exynos4_gpio2x_type_info);
+ type_register_static(&exynos4_gpio3_type_info);
+}
+
+type_init(exynos4_gpio_register_types)
diff --git a/hw/exynos4_boards.c b/hw/exynos4_boards.c
index 553a02b..b438361 100644
--- a/hw/exynos4_boards.c
+++ b/hw/exynos4_boards.c
@@ -149,7 +149,7 @@ static void smdkc210_init(ram_addr_t ram_size,
kernel_cmdline, initrd_filename, EXYNOS4_BOARD_SMDKC210);
lan9215_init(SMDK_LAN9118_BASE_ADDR,
- qemu_irq_invert(s->irq_table[exynos4210_get_irq(37, 1)]));
+ qdev_get_gpio_in(s->gpio2x, EXYNOS4210_GPIO2X_LINE(GPX0, 5)));
arm_load_kernel(first_cpu, &exynos4_board_binfo);
}
--
1.7.4.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [Qemu-devel] [PATCH V2 3/3] hw: add Atmel maxtouch touchscreen implementation
2012-03-15 7:35 [Qemu-devel] [PATCH V2 0/3] Exynos: i2c, gpio and touchscreen support for NURI board Igor Mitsyanko
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation Igor Mitsyanko
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 2/3] exynos4210: add exynos4210 GPIO implementation Igor Mitsyanko
@ 2012-03-15 7:35 ` Igor Mitsyanko
2012-03-15 9:48 ` Andreas Färber
2 siblings, 1 reply; 18+ messages in thread
From: Igor Mitsyanko @ 2012-03-15 7:35 UTC (permalink / raw)
To: qemu-devel
Cc: peter.maydell, Igor Mitsyanko, e.voevodin, kyungmin.park,
d.solodkiy, m.kozlov, afaerber
And use it for exynos4210 NURI board emulation
Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
---
Makefile.objs | 1 +
default-configs/arm-softmmu.mak | 1 +
hw/exynos4_boards.c | 11 +-
hw/maxtouch.c | 1079 +++++++++++++++++++++++++++++++++++++++
4 files changed, 1089 insertions(+), 3 deletions(-)
create mode 100644 hw/maxtouch.c
diff --git a/Makefile.objs b/Makefile.objs
index 226b01d..0171957 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -248,6 +248,7 @@ hw-obj-$(CONFIG_SMARTCARD) += usb/dev-smartcard-reader.o ccid-card-passthru.o
hw-obj-$(CONFIG_SMARTCARD_NSS) += ccid-card-emulated.o
hw-obj-$(CONFIG_USB_REDIR) += usb/redirect.o
hw-obj-$(CONFIG_I8259) += i8259_common.o i8259.o
+hw-obj-$(CONFIG_MAXTOUCH) += maxtouch.o
# PPC devices
hw-obj-$(CONFIG_PREP_PCI) += prep_pci.o
diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak
index e542b4f..7666748 100644
--- a/default-configs/arm-softmmu.mak
+++ b/default-configs/arm-softmmu.mak
@@ -27,3 +27,4 @@ CONFIG_SMC91C111=y
CONFIG_DS1338=y
CONFIG_PFLASH_CFI01=y
CONFIG_PFLASH_CFI02=y
+CONFIG_MAXTOUCH=y
diff --git a/hw/exynos4_boards.c b/hw/exynos4_boards.c
index b438361..f851026 100644
--- a/hw/exynos4_boards.c
+++ b/hw/exynos4_boards.c
@@ -28,6 +28,7 @@
#include "exec-memory.h"
#include "exynos4210.h"
#include "boards.h"
+#include "i2c.h"
#undef DEBUG
@@ -44,6 +45,7 @@
#endif
#define SMDK_LAN9118_BASE_ADDR 0x05000000
+#define MAXTOUCH_TS_I2C_ADDR 0x4a
typedef enum Exynos4BoardType {
EXYNOS4_BOARD_NURI,
@@ -134,9 +136,12 @@ static void nuri_init(ram_addr_t ram_size,
const char *kernel_filename, const char *kernel_cmdline,
const char *initrd_filename, const char *cpu_model)
{
- exynos4_boards_init_common(kernel_filename, kernel_cmdline,
- initrd_filename, EXYNOS4_BOARD_NURI);
-
+ Exynos4210State *cpu = exynos4_boards_init_common(kernel_filename,
+ kernel_cmdline, initrd_filename, EXYNOS4_BOARD_NURI);
+ DeviceState *dev =
+ i2c_create_slave(cpu->i2c_if[3], "maxtouch.var0", MAXTOUCH_TS_I2C_ADDR);
+ qdev_connect_gpio_out(dev, 0, qdev_get_gpio_in(cpu->gpio2x,
+ EXYNOS4210_GPIO2X_LINE(GPX0, 4)));
arm_load_kernel(first_cpu, &exynos4_board_binfo);
}
diff --git a/hw/maxtouch.c b/hw/maxtouch.c
new file mode 100644
index 0000000..0c37d30
--- /dev/null
+++ b/hw/maxtouch.c
@@ -0,0 +1,1079 @@
+/*
+ * Atmel maXTouch touchscreen emulation
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved.
+ * Igor Mitsyanko <i.mitsyanko@samsung.com>
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 "i2c.h"
+#include "console.h"
+
+#ifndef MXT_DEBUG
+#define MXT_DEBUG 0
+#endif
+
+/* Fifo length must be a power of 2 */
+#define MXT_MESSAGE_FIFO_LEN 16
+/* Maxtouch supports up to 10 concurrent touches, but we emulate 3 since common
+ * PC mouse has only 3 buttons. Exact meaning of each touch (each mouse button
+ * press) is defined by target userspace application only */
+#define MXT_NUM_OF_TOUCHES 3
+#define MXT_CRC_POLY 0x80001B
+#define MXT_CRC_SIZE 3
+/* Maximum value of x and y coordinate in QEMU mouse event callback */
+#define MXT_QEMU_MAX_COORD 0x7FFF
+
+/* Each maXTouch device consists of a certain number of subdevices (objects)
+ * with code names like T5, T6, T9, e.t.c. Each object implements only a portion
+ * of maXTouch functionality. For example, touch detection is performed
+ * by T9 object, but information about touch state changes is generated (and can
+ * be read) only in T5 object.
+ * Various variants of maXTouch can have different set of objects placed at
+ * different addresses within maXtouch address space. Composition of objects
+ * is described by mandatory Object Table which starts at address 0x7.
+ * Length of object table (i.e. number of objects) of this exact variant of
+ * maXTouch can be read from address 0x6 */
+#define MXT_OBJTBL_ENTRY_LEN 6
+/* Offsets within one object table entry */
+/* Object type code */
+#define MXT_OBJTBL_TYPE 0x0
+/* Start address of object registers within maxTouch address space */
+#define MXT_OBJTBL_START_LSB 0x1
+#define MXT_OBJTBL_START_MSB 0x2
+/* Number of object's registers (actually, this field contains size-1) */
+#define MXT_OBJTBL_SIZE 0x3
+/* Number of instances of this object (contains instances-1, so value 0 means
+ * one instance). All instances are placed continuously in memory */
+#define MXT_OBJTBL_INSTANCES 0x4
+/* Number of messages ID's object can generate in T5 object. For example,
+ * T9 will generate messages with different ID's for each separate touch */
+#define MXT_OBJTBL_REPORT_IDS 0x5
+
+/* Object types */
+enum {
+ MXT_GEN_MESSAGE_T5 = 0, MXT_GEN_COMMAND_T6,
+ MXT_GEN_POWER_T7, MXT_GEN_ACQUIRE_T8,
+ MXT_TOUCH_MULTI_T9, MXT_TOUCH_KEYARRAY_T15,
+ MXT_SPT_COMMSCONFIG_T18, MXT_SPT_GPIOPWM_T19,
+ MXT_PROCI_GRIPFACE_T20, MXT_PROCG_NOISE_T22,
+ MXT_TOUCH_PROXIMITY_T23, MXT_PROCI_ONETOUCH_T24,
+ MXT_SPT_SELFTEST_T25, MXT_PROCI_TWOTOUCH_T27,
+ MXT_SPT_CTECONFIG_T28, MXT_DEBUG_DIAGNOSTIC_T37,
+ MXT_NUM_OF_OBJECT_TYPES
+};
+
+static const uint8_t mxt_obj_types_list[MXT_NUM_OF_OBJECT_TYPES] = {
+ [MXT_GEN_MESSAGE_T5] = 5, [MXT_GEN_COMMAND_T6] = 6,
+ [MXT_GEN_POWER_T7] = 7, [MXT_GEN_ACQUIRE_T8] = 8,
+ [MXT_TOUCH_MULTI_T9] = 9, [MXT_TOUCH_KEYARRAY_T15] = 15,
+ [MXT_SPT_COMMSCONFIG_T18] = 18, [MXT_SPT_GPIOPWM_T19] = 19,
+ [MXT_PROCI_GRIPFACE_T20] = 20, [MXT_PROCG_NOISE_T22] = 22,
+ [MXT_TOUCH_PROXIMITY_T23] = 23, [MXT_PROCI_ONETOUCH_T24] = 24,
+ [MXT_SPT_SELFTEST_T25] = 25, [MXT_PROCI_TWOTOUCH_T27] = 27,
+ [MXT_SPT_CTECONFIG_T28] = 28, [MXT_DEBUG_DIAGNOSTIC_T37] = 37
+};
+
+static const uint8_t mxt_obj_sizes[MXT_NUM_OF_OBJECT_TYPES] = {
+ [MXT_GEN_MESSAGE_T5] = 10, [MXT_GEN_COMMAND_T6] = 6,
+ [MXT_GEN_POWER_T7] = 3, [MXT_GEN_ACQUIRE_T8] = 8,
+ [MXT_TOUCH_MULTI_T9] = 31, [MXT_TOUCH_KEYARRAY_T15] = 11,
+ [MXT_SPT_COMMSCONFIG_T18] = 2, [MXT_SPT_GPIOPWM_T19] = 16,
+ [MXT_PROCI_GRIPFACE_T20] = 12, [MXT_PROCG_NOISE_T22] = 17,
+ [MXT_TOUCH_PROXIMITY_T23] = 15, [MXT_PROCI_ONETOUCH_T24] = 19,
+ [MXT_SPT_SELFTEST_T25] = 14, [MXT_PROCI_TWOTOUCH_T27] = 7,
+ [MXT_SPT_CTECONFIG_T28] = 6, [MXT_DEBUG_DIAGNOSTIC_T37] = 128
+};
+
+#define MXT_INFO_START 0x00
+#define MXT_INFO_SIZE 7
+#define MXT_INFO_END (MXT_INFO_START + MXT_INFO_SIZE - 1)
+#define MXT_OBJTBL_START (MXT_INFO_START + MXT_INFO_SIZE)
+#define MXT_OBJTBL_END(s) \
+ (MXT_OBJTBL_START + (s->mxt_info[MXT_OBJ_NUM]) * MXT_OBJTBL_ENTRY_LEN - 1)
+#define MXT_CRC_START(s) \
+ (MXT_OBJTBL_START + (s->mxt_info[MXT_OBJ_NUM]) * MXT_OBJTBL_ENTRY_LEN)
+#define MXT_CRC_END(s) \
+ (MXT_OBJTBL_START + (s->mxt_info[MXT_OBJ_NUM]) * MXT_OBJTBL_ENTRY_LEN + \
+ MXT_CRC_SIZE - 1)
+#define MXT_OBJECTS_START(s) (s->obj_tbl[0].start_addr)
+
+/* MXT info Registers */
+#define MXT_FAMILY_ID 0x00
+#define MXT_VARIANT_ID 0x01
+#define MXT_VERSION 0x02
+#define MXT_BUILD 0x03
+#define MXT_MATRIX_X_SIZE 0x04
+#define MXT_MATRIX_Y_SIZE 0x05
+#define MXT_OBJ_NUM 0x06
+
+/* Registers within Multitouch T9 object */
+#define MXT_OBJ_T9_CTRL 0
+#define MXT_OBJ_T9_XORIGIN 1
+#define MXT_OBJ_T9_YORIGIN 2
+#define MXT_OBJ_T9_XSIZE 3
+#define MXT_OBJ_T9_YSIZE 4
+#define MXT_OBJ_T9_BLEN 6
+#define MXT_OBJ_T9_TCHTHR 7
+#define MXT_OBJ_T9_TCHDI 8
+#define MXT_OBJ_T9_ORIENT 9
+#define MXT_OBJ_T9_MOVHYSTI 11
+#define MXT_OBJ_T9_MOVHYSTN 12
+#define MXT_OBJ_T9_NUMTOUCH 14
+#define MXT_OBJ_T9_MRGHYST 15
+#define MXT_OBJ_T9_MRGTHR 16
+#define MXT_OBJ_T9_AMPHYST 17
+#define MXT_OBJ_T9_XRANGE_LSB 18
+#define MXT_OBJ_T9_XRANGE_MSB 19
+#define MXT_OBJ_T9_YRANGE_LSB 20
+#define MXT_OBJ_T9_YRANGE_MSB 21
+#define MXT_OBJ_T9_XLOCLIP 22
+#define MXT_OBJ_T9_XHICLIP 23
+#define MXT_OBJ_T9_YLOCLIP 24
+#define MXT_OBJ_T9_YHICLIP 25
+#define MXT_OBJ_T9_XEDGECTRL 26
+#define MXT_OBJ_T9_XEDGEDIST 27
+#define MXT_OBJ_T9_YEDGECTRL 28
+#define MXT_OBJ_T9_YEDGEDIST 29
+#define MXT_OBJ_T9_JUMPLIMIT 30
+
+/* Multitouch T9 messages status flags */
+#define MXT_T9_STAT_MOVE (1 << 4)
+#define MXT_T9_STAT_RELEASE (1 << 5)
+#define MXT_T9_STAT_PRESS (1 << 6)
+#define MXT_T9_STAT_DETECT (1 << 7)
+
+/* Multitouch T9 orient bits */
+#define MXT_T9_XY_SWITCH (1 << 0)
+
+/* Fields of T5 message */
+#define MXT_OBJ_T5_REPORTID 0
+#define MXT_OBJ_T5_STATUS 1
+#define MXT_OBJ_T5_XPOSMSH 2
+#define MXT_OBJ_T5_YPOSMSH 3
+#define MXT_OBJ_T5_XYPOSLSH 4
+#define MXT_OBJ_T5_AREA 5
+#define MXT_OBJ_T5_PRESSURE 6
+#define MXT_OBJ_T5_UNKNOWN 7
+#define MXT_OBJ_T5_CHECKSUM 8
+
+#if MXT_MESSAGE_FIFO_LEN & (MXT_MESSAGE_FIFO_LEN - 1)
+#error Message fifo length must be a power of 2
+#endif
+
+/* An entry of object description table */
+typedef struct MXTObjTblEntry {
+ uint8_t type;
+ uint16_t start_addr;
+ uint8_t size;
+ uint8_t instances;
+ uint8_t num_report_ids;
+} MXTObjTblEntry;
+
+typedef struct ObjConfig {
+ uint8_t type;
+ uint8_t instances;
+} ObjConfig;
+
+typedef struct MXTVariantInfo {
+ const char *name;
+ const uint8_t *mxt_variant_info;
+ const ObjConfig *mxt_variant_obj_list;
+} MXTVariantInfo;
+
+#define TYPE_MAXTOUCH "maxtouch"
+#define MXT(obj) \
+ OBJECT_CHECK(MXTState, (obj), TYPE_MAXTOUCH)
+#define MXT_CLASS(klass) \
+ OBJECT_CLASS_CHECK(MXTClass, (klass), TYPE_MAXTOUCH)
+#define MXT_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(MXTClass, (obj), TYPE_MAXTOUCH)
+
+/* Definitions for variant #0 of Atmel maXTouch */
+#define TYPE_MXT_VARIANT0 "maxtouch.var0"
+
+static ObjConfig mxt_variant0_objlist[] = {
+ { .type = MXT_GEN_MESSAGE_T5, .instances = 0 },
+ { .type = MXT_GEN_COMMAND_T6, .instances = 0 },
+ { .type = MXT_GEN_POWER_T7, .instances = 0 },
+ { .type = MXT_GEN_ACQUIRE_T8, .instances = 0 },
+ { .type = MXT_TOUCH_MULTI_T9, .instances = 0 },
+ { .type = MXT_TOUCH_KEYARRAY_T15, .instances = 0 },
+ { .type = MXT_SPT_GPIOPWM_T19, .instances = 0 },
+ { .type = MXT_PROCI_GRIPFACE_T20, .instances = 0 },
+ { .type = MXT_PROCG_NOISE_T22, .instances = 0 },
+ { .type = MXT_TOUCH_PROXIMITY_T23, .instances = 0 },
+ { .type = MXT_PROCI_ONETOUCH_T24, .instances = 0 },
+ { .type = MXT_SPT_SELFTEST_T25, .instances = 0 },
+ { .type = MXT_PROCI_TWOTOUCH_T27, .instances = 0 },
+ { .type = MXT_SPT_CTECONFIG_T28, .instances = 0 },
+};
+
+static const uint8_t mxt_variant0_info[MXT_INFO_SIZE] = {
+ [MXT_FAMILY_ID] = 0x80,
+ [MXT_VARIANT_ID] = 0x0,
+ [MXT_VERSION] = 0x1,
+ [MXT_BUILD] = 0x1,
+ [MXT_MATRIX_X_SIZE] = 16,
+ [MXT_MATRIX_Y_SIZE] = 14,
+ [MXT_OBJ_NUM] = sizeof(mxt_variant0_objlist) / sizeof(ObjConfig),
+};
+
+/* Definitions for variant #1 of Atmel maXTouch */
+#define TYPE_MXT_VARIANT1 "maxtouch.var1"
+
+static ObjConfig mxt_variant1_objlist[] = {
+ { .type = MXT_GEN_MESSAGE_T5, .instances = 0 },
+ { .type = MXT_GEN_COMMAND_T6, .instances = 0 },
+ { .type = MXT_GEN_POWER_T7, .instances = 0 },
+ { .type = MXT_GEN_ACQUIRE_T8, .instances = 0 },
+ { .type = MXT_TOUCH_MULTI_T9, .instances = 0 },
+ { .type = MXT_SPT_COMMSCONFIG_T18, .instances = 0 },
+ { .type = MXT_PROCI_GRIPFACE_T20, .instances = 0 },
+ { .type = MXT_PROCG_NOISE_T22, .instances = 0 },
+ { .type = MXT_SPT_CTECONFIG_T28, .instances = 0 },
+ { .type = MXT_DEBUG_DIAGNOSTIC_T37, .instances = 0 },
+};
+
+static const uint8_t mxt_variant1_info[MXT_INFO_SIZE] = {
+ [MXT_FAMILY_ID] = 0x80,
+ [MXT_VARIANT_ID] = 0x1,
+ [MXT_VERSION] = 0x1,
+ [MXT_BUILD] = 0x1,
+ [MXT_MATRIX_X_SIZE] = 16,
+ [MXT_MATRIX_Y_SIZE] = 14,
+ [MXT_OBJ_NUM] = sizeof(mxt_variant1_objlist) / sizeof(ObjConfig),
+};
+
+static const MXTVariantInfo mxt_variants_info_array[] = {
+ {
+ .name = TYPE_MXT_VARIANT0,
+ .mxt_variant_info = mxt_variant0_info,
+ .mxt_variant_obj_list = mxt_variant0_objlist
+ },
+ {
+ .name = TYPE_MXT_VARIANT1,
+ .mxt_variant_info = mxt_variant1_info,
+ .mxt_variant_obj_list = mxt_variant1_objlist
+ }
+};
+
+#define MXT_NUM_OF_VARIANTS \
+ (sizeof(mxt_variants_info_array) / sizeof(MXTVariantInfo))
+
+/* Generate Message T5 message format */
+typedef struct MXTMessage {
+ uint8_t reportid;
+ uint8_t status;
+ uint8_t xpos_msh;
+ uint8_t ypos_msh;
+ uint8_t xypos_lsh;
+ uint8_t area;
+ uint8_t pressure;
+ uint8_t checksum;
+} MXTMessage;
+
+/* Possible MXT i2c-related states */
+enum {
+ idle = 0,
+ start_bit_write,
+ start_bit_read,
+ next_is_reg_lsb,
+ next_is_reg_msb,
+ register_selected,
+ send_zero_next,
+ reading_data,
+};
+
+typedef struct MXTClass {
+ I2CSlaveClass parent_class;
+
+ const uint8_t *mxt_info;
+ MXTObjTblEntry *obj_tbl;
+ uint8_t crc[MXT_CRC_SIZE];
+ uint16_t end_addr;
+ /* This is used to speed things up a little */
+ uint16_t t5_address;
+ uint16_t t9_address;
+} MXTClass;
+
+typedef struct MXTState {
+ I2CSlave i2c;
+ QEMUPutMouseEntry *mouse;
+ qemu_irq nCHG; /* line state changes to low level to signal new event */
+ uint32_t i2c_state;
+ uint8_t *objects;
+
+ MXTMessage msg_fifo[MXT_MESSAGE_FIFO_LEN];
+ uint8_t fifo_get;
+ uint8_t fifo_add;
+ bool fifo_lock;
+
+ uint16_t selected_reg;
+ uint16_t scale_coef_x;
+ uint16_t scale_coef_y;
+ uint16_t x_offset;
+ uint16_t y_offset;
+ uint16_t x_curr;
+ uint16_t y_curr;
+ uint8_t touches;
+} MXTState;
+
+static const VMStateDescription mxt_message_vmstate = {
+ .name = "mxt-message",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(reportid, MXTMessage),
+ VMSTATE_UINT8(status, MXTMessage),
+ VMSTATE_UINT8(xpos_msh, MXTMessage),
+ VMSTATE_UINT8(ypos_msh, MXTMessage),
+ VMSTATE_UINT8(xypos_lsh, MXTMessage),
+ VMSTATE_UINT8(area, MXTMessage),
+ VMSTATE_UINT8(pressure, MXTMessage),
+ VMSTATE_UINT8(checksum, MXTMessage),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
+#if MXT_DEBUG
+#define DPRINT(fmt, args...) \
+ do {fprintf(stderr, "QEMU MXT: "fmt, ## args); } while (0)
+#define DPRINT_SMPL(fmt, args...) \
+ do {fprintf(stderr, fmt, ## args); } while (0)
+#define ERRPRINT(fmt, args...) \
+ do {fprintf(stderr, "QEMU MXT ERROR: "fmt, ## args); } while (0)
+
+static char dbg_reg_buf[50];
+
+static const char *dbg_multitoucht9_regs[] = {
+ [MXT_OBJ_T9_CTRL] = "CTRL", [MXT_OBJ_T9_XORIGIN] = "XORIGIN",
+ [MXT_OBJ_T9_YORIGIN] = "YORIGIN", [MXT_OBJ_T9_XSIZE] = "XSIZE",
+ [MXT_OBJ_T9_YSIZE] = "YSIZE", [5] = "REG_5",
+ [MXT_OBJ_T9_BLEN] = "BLEN", [MXT_OBJ_T9_TCHTHR] = "TCHTHR",
+ [MXT_OBJ_T9_TCHDI] = "TCHDI", [MXT_OBJ_T9_ORIENT] = "ORIENT",
+ [10] = "REG_10", [MXT_OBJ_T9_MOVHYSTI] = "MOVHYSTI",
+ [MXT_OBJ_T9_MOVHYSTN] = "MOVHYSTN", [13] = "REG_13",
+ [MXT_OBJ_T9_NUMTOUCH] = "NUMTOUCH",
+ [MXT_OBJ_T9_MRGHYST] = "MRGHYST", [MXT_OBJ_T9_MRGTHR] = "MRGTHR",
+ [MXT_OBJ_T9_AMPHYST] = "AMPHYST", [MXT_OBJ_T9_XRANGE_LSB] = "XRANGE_L",
+ [MXT_OBJ_T9_XRANGE_MSB] = "XRANGE_H", [MXT_OBJ_T9_YRANGE_LSB] = "YRANGE_L",
+ [MXT_OBJ_T9_YRANGE_MSB] = "YRANGE_H", [MXT_OBJ_T9_XLOCLIP] = "XLOCLIP",
+ [MXT_OBJ_T9_XHICLIP] = "XHICLIP", [MXT_OBJ_T9_YLOCLIP] = "YLOCLIP",
+ [MXT_OBJ_T9_YHICLIP] = "YHICLIP", [MXT_OBJ_T9_XEDGECTRL] = "XEDGECTRL",
+ [MXT_OBJ_T9_XEDGEDIST] = "XEDGEDIST", [MXT_OBJ_T9_YEDGECTRL] = "YEDGECTRL",
+ [MXT_OBJ_T9_YEDGEDIST] = "YEDGEDIST", [MXT_OBJ_T9_JUMPLIMIT] = "JUMPLIMIT",
+};
+
+static const char *dbg_gen_message_t5_regs[] = {
+ [MXT_OBJ_T5_REPORTID] = "REPORTID", [MXT_OBJ_T5_STATUS] = "STATUS",
+ [MXT_OBJ_T5_XPOSMSH] = "XPOSMSH", [MXT_OBJ_T5_YPOSMSH] = "YPOSMSH",
+ [MXT_OBJ_T5_XYPOSLSH] = "XYPOSLSH", [MXT_OBJ_T5_AREA] = "AREA",
+ [MXT_OBJ_T5_PRESSURE] = "PRESSURE", [MXT_OBJ_T5_UNKNOWN] = "REG_7",
+ [MXT_OBJ_T5_CHECKSUM] = "CHECKSUM", [9] = "REG_9"
+};
+
+static const char *dbg_mxt_info_regs[] = {
+ [MXT_FAMILY_ID] = "FAMILY_ID", [MXT_VARIANT_ID] = "VARIANT_ID",
+ [MXT_VERSION] = "VERSION", [MXT_BUILD] = "BUILD",
+ [MXT_MATRIX_X_SIZE] = "MATRIX_X_SIZE",
+ [MXT_MATRIX_Y_SIZE] = "MATRIX_Y_SIZE",
+ [MXT_OBJ_NUM] = "OBJ_NUM",
+};
+
+static const char *dbg_mxt_obj_name(unsigned type)
+{
+ switch (type) {
+ case 5:
+ return "GEN_MESSAGE_T5";
+ case 6:
+ return "GEN_COMMAND_T6";
+ case 7:
+ return "GEN_POWER_T7";
+ case 8:
+ return "GEN_ACQUIRE_T8";
+ case 9:
+ return "TOUCH_MULTI_T9";
+ case 15:
+ return "TOUCH_KEYARRAY_T15";
+ case 18:
+ return "SPT_COMMSCONFIG_T18";
+ case 19:
+ return "SPT_GPIOPWM_T19";
+ case 20:
+ return "PROCI_GRIPFACE_T20";
+ case 22:
+ return "PROCG_NOISE_T22";
+ case 23:
+ return "TOUCH_PROXIMITY_T23";
+ case 24:
+ return "PROCI_ONETOUCH_T24";
+ case 25:
+ return "SPT_SELFTEST_T25";
+ case 27:
+ return "PROCI_TWOTOUCH_T27";
+ case 28:
+ return "SPT_CTECONFIG_T28";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static char *mxt_get_reg_name(MXTState *s, unsigned int offset)
+{
+ MXTClass *k = MXT_GET_CLASS(s);
+ unsigned i;
+
+ if ((offset >= k->t5_address) &&
+ (offset < (k->t5_address + mxt_obj_sizes[MXT_GEN_MESSAGE_T5]))) {
+ i = (offset - k->t5_address);
+ snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "MESSAGE_T5[%s]",
+ dbg_gen_message_t5_regs[i]);
+ } else if ((offset >= k->t9_address) && (offset < k->t9_address +
+ mxt_obj_sizes[MXT_TOUCH_MULTI_T9])) {
+ i = (offset - k->t9_address);
+ snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "MULTITOUCH_T9[%s]",
+ dbg_multitoucht9_regs[i]);
+ } else if (offset <= MXT_INFO_END) {
+ i = (offset - MXT_INFO_START);
+ snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "INFO[%s]",
+ dbg_mxt_info_regs[i]);
+ } else if (offset <= MXT_OBJTBL_END(k)) {
+ i = (offset - MXT_OBJTBL_START) / MXT_OBJTBL_ENTRY_LEN;
+ switch ((offset - MXT_OBJTBL_START) % MXT_OBJTBL_ENTRY_LEN) {
+ case MXT_OBJTBL_TYPE:
+ snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "OBJTBL_%u TYPE", i);
+ break;
+ case MXT_OBJTBL_START_LSB:
+ snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "OBJTBL_%u ADDR_LSB", i);
+ break;
+ case MXT_OBJTBL_START_MSB:
+ snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "OBJTBL_%u ADDR_MSB", i);
+ break;
+ case MXT_OBJTBL_SIZE:
+ snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "OBJTBL_%u SIZE", i);
+ break;
+ case MXT_OBJTBL_INSTANCES:
+ snprintf(dbg_reg_buf, sizeof(dbg_reg_buf),
+ "OBJTBL_%u INSTANCES", i);
+ break;
+ case MXT_OBJTBL_REPORT_IDS:
+ snprintf(dbg_reg_buf, sizeof(dbg_reg_buf),
+ "OBJTBL_%u REPORT_IDS", i);
+ break;
+ }
+ } else if (offset <= MXT_CRC_END(k)) {
+ i = offset - MXT_CRC_START(k);
+ snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "CRC[%i]", i);
+ } else if (offset <= k->end_addr) {
+ for (i = 0; i < k->mxt_info[MXT_OBJ_NUM]; i++) {
+ if (offset >= (k->obj_tbl[i].start_addr + k->obj_tbl[i].size + 1)) {
+ continue;
+ }
+ snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "%s[%u]",
+ dbg_mxt_obj_name(k->obj_tbl[i].type),
+ offset - k->obj_tbl[i].start_addr);
+ break;
+ }
+ } else {
+ snprintf(dbg_reg_buf, sizeof(dbg_reg_buf), "UNKNOWN");
+ }
+ return dbg_reg_buf;
+}
+
+#else
+#define DPRINT(fmt, args...) do { } while (0)
+#define DPRINT_SMPL(fmt, args...) do { } while (0)
+#define ERRPRINT(fmt, args...) \
+ do {fprintf(stderr, "QEMU MXT ERROR: "fmt, ## args); } while (0)
+#endif
+
+static void
+mxt_mouse_event(void *opaque, int x, int y, int z, int buttons_state)
+{
+ MXTState *s = (MXTState *)opaque;
+ uint16_t x_new, y_new;
+ unsigned i;
+ bool state_changed = false;
+
+ /* Check that message buffer is not full */
+ if (s->fifo_lock ||
+ ((s->fifo_add + 1) & (MXT_MESSAGE_FIFO_LEN - 1)) == s->fifo_get) {
+ return;
+ }
+
+ x_new = (x + s->x_offset) / s->scale_coef_x;
+ y_new = (y + s->y_offset) / s->scale_coef_y;
+
+ for (i = 1; i <= MXT_NUM_OF_TOUCHES; i++) {
+ if ((s->touches & (1 << (i - 1))) != (buttons_state & (1 << (i - 1)))) {
+ if (buttons_state & (1 << (i - 1))) {
+ /* Generate press event message */
+ s->msg_fifo[s->fifo_add].status =
+ MXT_T9_STAT_DETECT | MXT_T9_STAT_PRESS;
+ s->msg_fifo[s->fifo_add].area = 0x20;
+ s->msg_fifo[s->fifo_add].pressure = 0x10;
+ } else {
+ /* Generate release event message */
+ s->msg_fifo[s->fifo_add].status = MXT_T9_STAT_RELEASE;
+ s->msg_fifo[s->fifo_add].area = 0x0;
+ s->msg_fifo[s->fifo_add].pressure = 0x0;
+ }
+ } else if ((s->touches & (1 << (i - 1))) &&
+ (s->x_curr != x_new || s->y_curr != y_new)) {
+ s->msg_fifo[s->fifo_add].status =
+ MXT_T9_STAT_DETECT | MXT_T9_STAT_MOVE;
+ s->msg_fifo[s->fifo_add].area = 0x20;
+ s->msg_fifo[s->fifo_add].pressure = 0x10;
+ } else {
+ continue;
+ }
+
+ s->msg_fifo[s->fifo_add].xpos_msh = x_new >> 4;
+ s->msg_fifo[s->fifo_add].ypos_msh = y_new >> 4;
+ s->msg_fifo[s->fifo_add].xypos_lsh =
+ (y_new & 0xF) | ((x_new << 4) & 0xF0);
+ s->msg_fifo[s->fifo_add].reportid = i;
+ s->msg_fifo[s->fifo_add].checksum = 0;
+ s->fifo_add = (s->fifo_add + 1) & (MXT_MESSAGE_FIFO_LEN - 1);
+ state_changed = true;
+ }
+
+ if (state_changed) {
+ s->touches = buttons_state;
+ s->x_curr = x_new;
+ s->y_curr = y_new;
+ /* CHG line changes to low and new message is generated in
+ * gen_message_t5 subsystem when touch event occurs. CHG line
+ * changes back to high only after all messages have been read from
+ * gen_message_t5 subsystem */
+ qemu_irq_lower(s->nCHG);
+ }
+}
+
+/* Read field of current message in message FIFO */
+static uint8_t mxt_read_message_field(MXTState *s, unsigned field)
+{
+ uint8_t ret;
+
+ /* If there are no messages, return dummy message with REPORTID=0xFF */
+ if (s->fifo_get == s->fifo_add) {
+ s->fifo_lock = true;
+ if (field == MXT_OBJ_T5_CHECKSUM) {
+ qemu_irq_raise(s->nCHG);
+ s->fifo_lock = false;
+ }
+ return 0xFF;
+ }
+
+ switch (field) {
+ case MXT_OBJ_T5_REPORTID:
+ return s->msg_fifo[s->fifo_get].reportid;
+ case MXT_OBJ_T5_STATUS:
+ return s->msg_fifo[s->fifo_get].status;
+ case MXT_OBJ_T5_XPOSMSH:
+ return s->msg_fifo[s->fifo_get].xpos_msh;
+ case MXT_OBJ_T5_YPOSMSH:
+ return s->msg_fifo[s->fifo_get].ypos_msh;
+ case MXT_OBJ_T5_XYPOSLSH:
+ return s->msg_fifo[s->fifo_get].xypos_lsh;
+ case MXT_OBJ_T5_AREA:
+ return s->msg_fifo[s->fifo_get].area;
+ case MXT_OBJ_T5_PRESSURE:
+ return s->msg_fifo[s->fifo_get].pressure;
+ case MXT_OBJ_T5_UNKNOWN:
+ /* Linux driver doesn't use this field */
+ return 0;
+ case MXT_OBJ_T5_CHECKSUM:
+ ret = s->msg_fifo[s->fifo_get].checksum;
+ s->fifo_get = (s->fifo_get + 1) & (MXT_MESSAGE_FIFO_LEN - 1);
+ return ret;
+ }
+ return 0;
+}
+
+static int mxt_read_info_reg(MXTClass *k, unsigned int offset)
+{
+ unsigned i;
+
+ if (offset <= MXT_INFO_END) {
+ return k->mxt_info[offset - MXT_INFO_START];
+ } else if (offset <= MXT_OBJTBL_END(k)) {
+ i = (offset - MXT_OBJTBL_START) / MXT_OBJTBL_ENTRY_LEN;
+ switch ((offset - MXT_OBJTBL_START) % MXT_OBJTBL_ENTRY_LEN) {
+ case MXT_OBJTBL_TYPE:
+ return k->obj_tbl[i].type;
+ case MXT_OBJTBL_START_LSB:
+ return (uint8_t)k->obj_tbl[i].start_addr;
+ case MXT_OBJTBL_START_MSB:
+ return (uint8_t)(k->obj_tbl[i].start_addr >> 8);
+ case MXT_OBJTBL_SIZE:
+ return k->obj_tbl[i].size;
+ case MXT_OBJTBL_INSTANCES:
+ return k->obj_tbl[i].instances;
+ case MXT_OBJTBL_REPORT_IDS:
+ return k->obj_tbl[i].num_report_ids;
+ }
+ } else if (offset <= MXT_CRC_END(k)) {
+ return k->crc[offset - MXT_CRC_START(k)];
+ }
+
+ return -1;
+}
+
+static inline void mxt_calc_x_scalecoef(MXTState *s)
+{
+ MXTClass *k = MXT_GET_CLASS(s);
+ uint16_t div, tmp;
+ uint8_t *t9 = &s->objects[k->t9_address - MXT_OBJECTS_START(k)];
+ uint16_t x_max = t9[MXT_OBJ_T9_XRANGE_LSB] |
+ (t9[MXT_OBJ_T9_XRANGE_MSB] << 8);
+
+ if (x_max == 0) {
+ if (t9[MXT_OBJ_T9_ORIENT] & MXT_T9_XY_SWITCH) {
+ s->scale_coef_y = 0xFFFF;
+ s->y_offset = 0;
+ } else {
+ s->scale_coef_x = 0xFFFF;
+ s->x_offset = 0;
+ }
+ return;
+ }
+
+ div = MXT_QEMU_MAX_COORD / x_max + 1;
+ tmp = x_max * div;
+ /* Divide by 4 if XRANGE less then 1024 */
+ if ((t9[MXT_OBJ_T9_YRANGE_MSB] & 0xC) == 0) {
+ div >>= 2;
+ div++;
+ tmp = x_max * (div << 2);
+ }
+
+ if (t9[MXT_OBJ_T9_ORIENT] & MXT_T9_XY_SWITCH) {
+ s->scale_coef_y = div;
+ s->y_offset = tmp - MXT_QEMU_MAX_COORD;
+ } else {
+ s->scale_coef_x = div;
+ s->x_offset = tmp - MXT_QEMU_MAX_COORD;
+ }
+}
+
+static inline void mxt_calc_y_scalecoef(MXTState *s)
+{
+ MXTClass *k = MXT_GET_CLASS(s);
+ uint16_t div, tmp;
+ uint8_t *t9 = &s->objects[k->t9_address - MXT_OBJECTS_START(k)];
+ uint16_t y_max =
+ t9[MXT_OBJ_T9_YRANGE_LSB] | (t9[MXT_OBJ_T9_YRANGE_MSB] << 8);
+
+ if (y_max == 0) {
+ if (t9[MXT_OBJ_T9_ORIENT] & MXT_T9_XY_SWITCH) {
+ s->scale_coef_x = 0xFFFF;
+ s->x_offset = 0;
+ } else {
+ s->scale_coef_y = 0xFFFF;
+ s->y_offset = 0;
+ }
+ return;
+ }
+
+ div = MXT_QEMU_MAX_COORD / y_max + 1;
+ tmp = y_max * div;
+ /* Divide by 4 if YRANGE less then 1024 */
+ if ((t9[MXT_OBJ_T9_YRANGE_MSB] & 0xC) == 0) {
+ div >>= 2;
+ div++;
+ tmp = y_max * (div << 2);
+ }
+
+ if (t9[MXT_OBJ_T9_ORIENT] & MXT_T9_XY_SWITCH) {
+ s->scale_coef_x = div;
+ s->x_offset = tmp - MXT_QEMU_MAX_COORD;
+ } else {
+ s->scale_coef_y = div;
+ s->y_offset = tmp - MXT_QEMU_MAX_COORD;
+ }
+}
+
+static void mxt_write_to_t9(MXTState *s, unsigned int offset, uint8_t val)
+{
+ MXTClass *k = MXT_GET_CLASS(s);
+ uint16_t addr = k->t9_address - MXT_OBJECTS_START(k) + offset;
+
+ s->objects[addr] = val;
+
+ switch (offset) {
+ case MXT_OBJ_T9_CTRL:
+ if ((s->objects[addr] == 0x83) && !(s->mouse)) {
+ s->mouse =
+ qemu_add_mouse_event_handler(mxt_mouse_event, s, 1, "maxtouch");
+ qemu_activate_mouse_event_handler(s->mouse);
+ } else if (s->objects[addr] == 0 && s->mouse) {
+ qemu_remove_mouse_event_handler(s->mouse);
+ s->mouse = NULL;
+ }
+ break;
+ case MXT_OBJ_T9_XRANGE_LSB: case MXT_OBJ_T9_XRANGE_MSB:
+ mxt_calc_x_scalecoef(s);
+ break;
+ case MXT_OBJ_T9_YRANGE_LSB: case MXT_OBJ_T9_YRANGE_MSB:
+ mxt_calc_y_scalecoef(s);
+ break;
+ case MXT_OBJ_T9_ORIENT:
+ mxt_calc_x_scalecoef(s);
+ mxt_calc_y_scalecoef(s);
+ break;
+ }
+}
+
+/* Atmel maXTouch i2c registers read byte sequence:
+ * <Start bit>
+ * [MXT i2c address(0x4A) with last bit 0(write data)]
+ * [LSB of MXT register offset (starting from 0)]
+ * [MSB of MXT register offset]
+ * <Stop bit>
+ * <Start bit>
+ * [MXT address(0x4A) with last bit 1(read data)]
+ * [MXT sends 0x0]
+ * [MXT sends value of register offset]
+ * [MXT sends value of register offset+1]
+ * [MXT sends value of register offset+2]
+ * [...........]
+ * <Stop bit>
+ *
+ * Atmel maXTouch i2c registers write byte sequence:
+ * <Start bit>
+ * [MXT address(0x4A) with last bit 0(write data)]
+ * [LSB of MXT register offset (starting from 0)]
+ * [MSB of MXT register offset]
+ * [value to write into register with specified offset]
+ * [value to write into register with specified offset+1]
+ * [value to write into register with specified offset+2]
+ * [...........]
+ * <Stop bit>
+ */
+
+static int mxt_i2c_read(I2CSlave *i2c)
+{
+ MXTState *s = MXT(i2c);
+ MXTClass *k = MXT_GET_CLASS(s);
+ int ret;
+
+ switch (s->i2c_state) {
+ case send_zero_next:
+ ret = 0;
+ s->i2c_state = reading_data;
+ break;
+ case reading_data:
+ if ((s->selected_reg >= k->t5_address) && (s->selected_reg <
+ (k->t5_address + mxt_obj_sizes[MXT_GEN_MESSAGE_T5]))) {
+ /* This is first because this will succeed almost always */
+ ret = mxt_read_message_field(s, s->selected_reg - k->t5_address);
+ } else if (s->selected_reg <= MXT_CRC_END(k)) {
+ ret = mxt_read_info_reg(k, s->selected_reg);
+ } else if (s->selected_reg <= k->end_addr) {
+ ret = s->objects[s->selected_reg - MXT_OBJECTS_START(k)];
+ } else {
+ ERRPRINT("register with address 0x%04x doesn't exist\n",
+ s->selected_reg);
+ ret = -1;
+ }
+ DPRINT("Sending %s(0x%02x) -> 0x%02x\n",
+ mxt_get_reg_name(s, s->selected_reg), s->selected_reg, ret);
+ s->selected_reg++;
+ break;
+ default:
+ ERRPRINT("data read request in wrong state!\n");
+ ret = -1;
+ }
+
+ return ret;
+}
+
+static int mxt_i2c_write(I2CSlave *i2c, uint8_t data)
+{
+ MXTState *s = MXT(i2c);
+ MXTClass *k = MXT_GET_CLASS(s);
+
+ switch (s->i2c_state) {
+ case start_bit_write:
+ /* data is i2c slave address, we can ignore it */
+ s->i2c_state = next_is_reg_lsb;
+ break;
+ case start_bit_read:
+ /* data is i2c slave address, we can ignore it */
+ s->i2c_state = send_zero_next;
+ break;
+ case next_is_reg_lsb:
+ s->selected_reg = (s->selected_reg & 0xFF00) | data;
+ s->i2c_state = next_is_reg_msb;
+ break;
+ case next_is_reg_msb:
+ s->selected_reg = (s->selected_reg & 0x00FF) | (data << 8);
+ DPRINT("Selected register 0x%04x\n", s->selected_reg);
+ s->i2c_state = register_selected;
+ break;
+ case register_selected:
+ DPRINT("Writing %s <- 0x%02x\n",
+ mxt_get_reg_name(s, s->selected_reg), data);
+ if ((s->selected_reg >= k->t9_address) && (s->selected_reg <
+ k->t9_address + mxt_obj_sizes[MXT_TOUCH_MULTI_T9])) {
+ mxt_write_to_t9(s, s->selected_reg - k->t9_address, data);
+ } else if ((s->selected_reg >= MXT_OBJECTS_START(k)) &&
+ (s->selected_reg <= k->end_addr)) {
+ s->objects[s->selected_reg - MXT_OBJECTS_START(k)] = data;
+ } else {
+ ERRPRINT("can't write to register with address 0x%04x\n",
+ s->selected_reg);
+ return -1;
+ }
+ s->selected_reg++;
+ break;
+ default:
+ ERRPRINT("data write request in wrong state!\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void mxt_i2c_event(I2CSlave *i2c, enum i2c_event event)
+{
+ MXTState *s = MXT(i2c);
+
+ switch (event) {
+ case I2C_START_RECV:
+ DPRINT("I2C start bit appeared: reading data\n");
+ s->i2c_state = start_bit_read;
+ break;
+ case I2C_START_SEND:
+ DPRINT("I2C start bit appeared: writing data\n");
+ s->i2c_state = start_bit_write;
+ break;
+ case I2C_FINISH:
+ DPRINT("I2C stop bit received\n");
+ s->i2c_state = idle;
+ break;
+ default:
+ break;
+ }
+}
+
+static void mxt_init(Object *obj)
+{
+ MXTState *s = MXT(obj);
+ MXTClass *k = MXT_GET_CLASS(obj);
+ unsigned i;
+ uint16_t objects_len = 0;
+
+ for (i = 0; i < k->mxt_info[MXT_OBJ_NUM]; i++) {
+ objects_len += (k->obj_tbl[i].size + 1) * (k->obj_tbl[i].instances + 1);
+ }
+ s->objects = g_malloc0(objects_len);
+
+ s->i2c_state = idle;
+ s->selected_reg = 0;
+ s->fifo_add = s->fifo_get = 0;
+ s->fifo_lock = false;
+ s->scale_coef_x = 0xFFFF;
+ s->scale_coef_y = 0xFFFF;
+ s->x_offset = 0;
+ s->y_offset = 0;
+ s->x_curr = 0;
+ s->y_curr = 0;
+ s->touches = 0;
+ s->mouse = NULL;
+
+ qdev_init_gpio_out(DEVICE(obj), &s->nCHG, 1);
+ qemu_irq_raise(s->nCHG);
+}
+
+static void mxt_finalize(Object *obj)
+{
+ MXTState *s = MXT(obj);
+
+ if (s->mouse) {
+ qemu_remove_mouse_event_handler(s->mouse);
+ s->mouse = NULL;
+ }
+
+ g_free(s->objects);
+}
+
+static int mxt_i2c_init(I2CSlave *i2c)
+{
+ return 0;
+}
+
+static inline uint32_t mxt_crc24(uint32_t crc, uint8_t byte1, uint8_t byte2)
+{
+ uint32_t ret = (crc << 1) ^ ((byte2 << 8) | byte1);
+
+ if (ret & 0x1000000) {
+ ret ^= MXT_CRC_POLY;
+ }
+
+ return ret;
+}
+
+static void mxt_calculate_crc(MXTClass *k)
+{
+ unsigned i;
+ uint32_t crc = 0;
+
+ for (i = 0; i < MXT_OBJTBL_END(k); i += 2) {
+ crc =
+ mxt_crc24(crc, mxt_read_info_reg(k, i), mxt_read_info_reg(k, i + 1));
+ }
+
+ crc = mxt_crc24(crc, mxt_read_info_reg(k, i), 0) & 0x00FFFFFF;
+ k->crc[0] = crc & 0xFF;
+ k->crc[1] = (crc >> 8) & 0xFF;
+ k->crc[2] = (crc >> 16) & 0xFF;
+}
+
+static void
+mxt_init_object_table(MXTClass *k, const ObjConfig *list)
+{
+ MXTObjTblEntry *tbl = k->obj_tbl;
+ unsigned i, tbl_len = k->mxt_info[MXT_OBJ_NUM];
+
+ for (i = 0; i < tbl_len; i++) {
+ tbl[i].type = mxt_obj_types_list[list[i].type];
+ tbl[i].size = mxt_obj_sizes[list[i].type] - 1;
+ tbl[i].instances = list[i].instances;
+ tbl[i].num_report_ids =
+ (list[i].type == MXT_TOUCH_MULTI_T9) ? MXT_NUM_OF_TOUCHES : 0;
+ if (i == 0) {
+ tbl[i].start_addr = MXT_OBJTBL_START + tbl_len *
+ MXT_OBJTBL_ENTRY_LEN + MXT_CRC_SIZE;
+ } else {
+ tbl[i].start_addr = tbl[i-1].start_addr +
+ (tbl[i-1].size + 1) * (tbl[i-1].instances + 1);
+ }
+ if (list[i].type == MXT_GEN_MESSAGE_T5) {
+ k->t5_address = tbl[i].start_addr;
+ } else if (list[i].type == MXT_TOUCH_MULTI_T9) {
+ k->t9_address = tbl[i].start_addr;
+ }
+ }
+
+ k->end_addr = tbl[i-1].start_addr + tbl[i-1].size;
+ /* T5 and T9 objects are mandatory */
+ assert(k->t5_address);
+ assert(k->t9_address);
+}
+
+static int mxt_post_load(void *opaque, int ver_id)
+{
+ MXTState *s = (MXTState *)opaque;
+ MXTClass *k = MXT_GET_CLASS(s);
+ uint16_t addr = k->t9_address - MXT_OBJECTS_START(k) + MXT_OBJ_T9_CTRL;
+
+ if ((s->objects[addr] == 0x83) && !(s->mouse)) {
+ s->mouse =
+ qemu_add_mouse_event_handler(mxt_mouse_event, s, 1, "maxtouch");
+ qemu_activate_mouse_event_handler(s->mouse);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription mxt_vmstate = {
+ .name = "mxt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = mxt_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(i2c_state, MXTState),
+ VMSTATE_STRUCT_ARRAY(msg_fifo, MXTState, MXT_MESSAGE_FIFO_LEN, 1,
+ mxt_message_vmstate, MXTMessage),
+ VMSTATE_UINT8(fifo_get, MXTState),
+ VMSTATE_UINT8(fifo_add, MXTState),
+ VMSTATE_BOOL(fifo_lock, MXTState),
+ VMSTATE_UINT16(selected_reg, MXTState),
+ VMSTATE_UINT16(scale_coef_x, MXTState),
+ VMSTATE_UINT16(scale_coef_y, MXTState),
+ VMSTATE_UINT16(x_offset, MXTState),
+ VMSTATE_UINT16(y_offset, MXTState),
+ VMSTATE_UINT16(x_curr, MXTState),
+ VMSTATE_UINT16(y_curr, MXTState),
+ VMSTATE_UINT8(touches, MXTState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void maxtouch_class_init(ObjectClass *klass, void *data)
+{
+ I2CSlaveClass *i2c = I2C_SLAVE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ MXTClass *k = MXT_CLASS(klass);
+ const MXTVariantInfo *info = (const MXTVariantInfo *)data;
+
+ dc->vmsd = &mxt_vmstate;
+ i2c->init = mxt_i2c_init;
+ i2c->event = mxt_i2c_event;
+ i2c->recv = mxt_i2c_read;
+ i2c->send = mxt_i2c_write;
+
+ k->mxt_info = info->mxt_variant_info;
+ k->obj_tbl = g_new0(MXTObjTblEntry, k->mxt_info[MXT_OBJ_NUM]);
+ mxt_init_object_table(k, info->mxt_variant_obj_list);
+ mxt_calculate_crc(k);
+}
+
+static void maxtouch_class_finalize(ObjectClass *klass, void *data)
+{
+ MXTClass *k = MXT_CLASS(klass);
+
+ g_free(k->obj_tbl);
+}
+
+static void mxt_register_type(const MXTVariantInfo *info)
+{
+ TypeInfo type = {
+ .name = info->name,
+ .parent = TYPE_MAXTOUCH,
+ .class_init = maxtouch_class_init,
+ .class_finalize = maxtouch_class_finalize,
+ .class_data = (void *)info
+ };
+
+ type_register(&type);
+}
+
+static TypeInfo maxtouch_type_info = {
+ .name = TYPE_MAXTOUCH,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(MXTState),
+ .instance_init = mxt_init,
+ .instance_finalize = mxt_finalize,
+ .abstract = true,
+ .class_size = sizeof(MXTClass),
+};
+
+static void mxt_types_init(void)
+{
+ unsigned i;
+
+ type_register_static(&maxtouch_type_info);
+ for (i = 0; i < MXT_NUM_OF_VARIANTS; i++) {
+ mxt_register_type(&mxt_variants_info_array[i]);
+ }
+}
+
+type_init(mxt_types_init)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 3/3] hw: add Atmel maxtouch touchscreen implementation
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 3/3] hw: add Atmel maxtouch touchscreen implementation Igor Mitsyanko
@ 2012-03-15 9:48 ` Andreas Färber
2012-03-15 10:25 ` Dmitry Solodkiy
2012-03-15 10:41 ` Igor Mitsyanko
0 siblings, 2 replies; 18+ messages in thread
From: Andreas Färber @ 2012-03-15 9:48 UTC (permalink / raw)
To: Igor Mitsyanko
Cc: peter.maydell, e.voevodin, qemu-devel, kyungmin.park, d.solodkiy,
m.kozlov
Am 15.03.2012 08:35, schrieb Igor Mitsyanko:
> And use it for exynos4210 NURI board emulation
>
> Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
Thanks for moving this to libhw. Looks okay, not knowing I2C or the
device and assuming you've tested it; some nitpicks below.
> diff --git a/hw/exynos4_boards.c b/hw/exynos4_boards.c
> index b438361..f851026 100644
> --- a/hw/exynos4_boards.c
> +++ b/hw/exynos4_boards.c
> @@ -134,9 +136,12 @@ static void nuri_init(ram_addr_t ram_size,
> const char *kernel_filename, const char *kernel_cmdline,
> const char *initrd_filename, const char *cpu_model)
> {
> - exynos4_boards_init_common(kernel_filename, kernel_cmdline,
> - initrd_filename, EXYNOS4_BOARD_NURI);
> -
> + Exynos4210State *cpu = exynos4_boards_init_common(kernel_filename,
> + kernel_cmdline, initrd_filename, EXYNOS4_BOARD_NURI);
I'd advise against using "cpu" for what looks like board or SoC state to
avoid confusion. "cpu" is going to be used for CPUState instances, i.e.
one day: ARMCPU *cpu = cpu_arm_init("cortex-a9");
I'd suggest s (generically for state) here.
> + DeviceState *dev =
> + i2c_create_slave(cpu->i2c_if[3], "maxtouch.var0", MAXTOUCH_TS_I2C_ADDR);
> + qdev_connect_gpio_out(dev, 0, qdev_get_gpio_in(cpu->gpio2x,
> + EXYNOS4210_GPIO2X_LINE(GPX0, 4)));
> arm_load_kernel(first_cpu, &exynos4_board_binfo);
> }
>
> diff --git a/hw/maxtouch.c b/hw/maxtouch.c
> new file mode 100644
> index 0000000..0c37d30
> --- /dev/null
> +++ b/hw/maxtouch.c
> @@ -0,0 +1,1079 @@
> +/*
> + * Atmel maXTouch touchscreen emulation
> + *
> + * Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved.
It's been remarked that "All rights reserved." conflicts with the GPL's
copyleft. Please drop that sentence if you can.
> + * Igor Mitsyanko <i.mitsyanko@samsung.com>
> + *
> + * 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 of the License, or
> + * (at your option) any later version.
> + *
> + * 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/>.
> + *
> + */
> +/* Object types */
> +enum {
> + MXT_GEN_MESSAGE_T5 = 0, MXT_GEN_COMMAND_T6,
> + MXT_GEN_POWER_T7, MXT_GEN_ACQUIRE_T8,
> + MXT_TOUCH_MULTI_T9, MXT_TOUCH_KEYARRAY_T15,
> + MXT_SPT_COMMSCONFIG_T18, MXT_SPT_GPIOPWM_T19,
> + MXT_PROCI_GRIPFACE_T20, MXT_PROCG_NOISE_T22,
> + MXT_TOUCH_PROXIMITY_T23, MXT_PROCI_ONETOUCH_T24,
> + MXT_SPT_SELFTEST_T25, MXT_PROCI_TWOTOUCH_T27,
> + MXT_SPT_CTECONFIG_T28, MXT_DEBUG_DIAGNOSTIC_T37,
> + MXT_NUM_OF_OBJECT_TYPES
> +};
Personally I find it easier to read a mostly monotonically increasing
enum (or array below) when it's not in two columns. Was this copied from
some Linux header as is so that we wouldn't want to reformat it to allow
for easy updates, or was this a design choice of yours?
> +#define TYPE_MAXTOUCH "maxtouch"
> +#define MXT(obj) \
> + OBJECT_CHECK(MXTState, (obj), TYPE_MAXTOUCH)
> +#define MXT_CLASS(klass) \
> + OBJECT_CLASS_CHECK(MXTClass, (klass), TYPE_MAXTOUCH)
> +#define MXT_GET_CLASS(obj) \
> + OBJECT_GET_CLASS(MXTClass, (obj), TYPE_MAXTOUCH)
> +static const uint8_t mxt_variant0_info[MXT_INFO_SIZE] = {
> + [MXT_FAMILY_ID] = 0x80,
> + [MXT_VARIANT_ID] = 0x0,
> + [MXT_VERSION] = 0x1,
> + [MXT_BUILD] = 0x1,
> + [MXT_MATRIX_X_SIZE] = 16,
> + [MXT_MATRIX_Y_SIZE] = 14,
> + [MXT_OBJ_NUM] = sizeof(mxt_variant0_objlist) / sizeof(ObjConfig),
We have an ARRAY_SIZE() macro for this.
> +};
> +static const uint8_t mxt_variant1_info[MXT_INFO_SIZE] = {
> + [MXT_FAMILY_ID] = 0x80,
> + [MXT_VARIANT_ID] = 0x1,
> + [MXT_VERSION] = 0x1,
> + [MXT_BUILD] = 0x1,
> + [MXT_MATRIX_X_SIZE] = 16,
> + [MXT_MATRIX_Y_SIZE] = 14,
> + [MXT_OBJ_NUM] = sizeof(mxt_variant1_objlist) / sizeof(ObjConfig),
Dito.
> +};
> +
> +static const MXTVariantInfo mxt_variants_info_array[] = {
> + {
> + .name = TYPE_MXT_VARIANT0,
> + .mxt_variant_info = mxt_variant0_info,
> + .mxt_variant_obj_list = mxt_variant0_objlist
> + },
> + {
> + .name = TYPE_MXT_VARIANT1,
> + .mxt_variant_info = mxt_variant1_info,
> + .mxt_variant_obj_list = mxt_variant1_objlist
> + }
> +};
> +
> +#define MXT_NUM_OF_VARIANTS \
> + (sizeof(mxt_variants_info_array) / sizeof(MXTVariantInfo))
Dito.
> +#if MXT_DEBUG
> +#define DPRINT(fmt, args...) \
> + do {fprintf(stderr, "QEMU MXT: "fmt, ## args); } while (0)
> +#define DPRINT_SMPL(fmt, args...) \
> + do {fprintf(stderr, fmt, ## args); } while (0)
> +#define ERRPRINT(fmt, args...) \
> + do {fprintf(stderr, "QEMU MXT ERROR: "fmt, ## args); } while (0)
Please insert a space after do {. Also somewhere below.
> +static TypeInfo maxtouch_type_info = {
Can be static const.
> + .name = TYPE_MAXTOUCH,
> + .parent = TYPE_I2C_SLAVE,
> + .instance_size = sizeof(MXTState),
> + .instance_init = mxt_init,
> + .instance_finalize = mxt_finalize,
> + .abstract = true,
> + .class_size = sizeof(MXTClass),
> +};
> +
> +static void mxt_types_init(void)
Please name it mxt_register_types by convention.
> +{
> + unsigned i;
> +
> + type_register_static(&maxtouch_type_info);
> + for (i = 0; i < MXT_NUM_OF_VARIANTS; i++) {
> + mxt_register_type(&mxt_variants_info_array[i]);
> + }
> +}
> +
> +type_init(mxt_types_init)
Andreas
--
SUSE LINUX Products GmbH, Maxfeldstr. 5, 90409 Nürnberg, Germany
GF: Jeff Hawn, Jennifer Guild, Felix Imendörffer; HRB 16746 AG Nürnberg
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 3/3] hw: add Atmel maxtouch touchscreen implementation
2012-03-15 9:48 ` Andreas Färber
@ 2012-03-15 10:25 ` Dmitry Solodkiy
2012-03-15 10:41 ` Igor Mitsyanko
1 sibling, 0 replies; 18+ messages in thread
From: Dmitry Solodkiy @ 2012-03-15 10:25 UTC (permalink / raw)
To: 'Andreas Färber'
Cc: peter.maydell, 'Igor Mitsyanko', e.voevodin, qemu-devel,
kyungmin.park, m.kozlov
Dear Andreas,
"All rights reserved" was added by accident and going to be removed on next
update of all our patches.
Thanks,
Dmitry Solodkiy,
Emulator/Kernel PL, Mobile Group,
Moscow R&D center, Samsung Electronics
-----Original Message-----
From: Andreas Färber [mailto:afaerber@suse.de]
Sent: Thursday, March 15, 2012 1:48 PM
To: Igor Mitsyanko
Cc: qemu-devel@nongnu.org; peter.maydell@linaro.org; e.voevodin@samsung.com;
kyungmin.park@samsung.com; d.solodkiy@samsung.com; m.kozlov@samsung.com
Subject: Re: [Qemu-devel] [PATCH V2 3/3] hw: add Atmel maxtouch touchscreen
implementation
Am 15.03.2012 08:35, schrieb Igor Mitsyanko:
> And use it for exynos4210 NURI board emulation
>
> Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
Thanks for moving this to libhw. Looks okay, not knowing I2C or the device and
assuming you've tested it; some nitpicks below.
> diff --git a/hw/exynos4_boards.c b/hw/exynos4_boards.c index
> b438361..f851026 100644
> --- a/hw/exynos4_boards.c
> +++ b/hw/exynos4_boards.c
> @@ -134,9 +136,12 @@ static void nuri_init(ram_addr_t ram_size,
> const char *kernel_filename, const char *kernel_cmdline,
> const char *initrd_filename, const char *cpu_model) {
> - exynos4_boards_init_common(kernel_filename, kernel_cmdline,
> - initrd_filename, EXYNOS4_BOARD_NURI);
> -
> + Exynos4210State *cpu = exynos4_boards_init_common(kernel_filename,
> + kernel_cmdline, initrd_filename, EXYNOS4_BOARD_NURI);
I'd advise against using "cpu" for what looks like board or SoC state to avoid
confusion. "cpu" is going to be used for CPUState instances, i.e.
one day: ARMCPU *cpu = cpu_arm_init("cortex-a9");
I'd suggest s (generically for state) here.
> + DeviceState *dev =
> + i2c_create_slave(cpu->i2c_if[3], "maxtouch.var0",
MAXTOUCH_TS_I2C_ADDR);
> + qdev_connect_gpio_out(dev, 0, qdev_get_gpio_in(cpu->gpio2x,
> + EXYNOS4210_GPIO2X_LINE(GPX0, 4)));
> arm_load_kernel(first_cpu, &exynos4_board_binfo); }
>
> diff --git a/hw/maxtouch.c b/hw/maxtouch.c new file mode 100644 index
> 0000000..0c37d30
> --- /dev/null
> +++ b/hw/maxtouch.c
> @@ -0,0 +1,1079 @@
> +/*
> + * Atmel maXTouch touchscreen emulation
> + *
> + * Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved.
It's been remarked that "All rights reserved." conflicts with the GPL's
copyleft. Please drop that sentence if you can.
> + * Igor Mitsyanko <i.mitsyanko@samsung.com>
> + *
> + * 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 of the License, or
> + * (at your option) any later version.
> + *
> + * 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/>.
> + *
> + */
> +/* Object types */
> +enum {
> + MXT_GEN_MESSAGE_T5 = 0, MXT_GEN_COMMAND_T6,
> + MXT_GEN_POWER_T7, MXT_GEN_ACQUIRE_T8,
> + MXT_TOUCH_MULTI_T9, MXT_TOUCH_KEYARRAY_T15,
> + MXT_SPT_COMMSCONFIG_T18, MXT_SPT_GPIOPWM_T19,
> + MXT_PROCI_GRIPFACE_T20, MXT_PROCG_NOISE_T22,
> + MXT_TOUCH_PROXIMITY_T23, MXT_PROCI_ONETOUCH_T24,
> + MXT_SPT_SELFTEST_T25, MXT_PROCI_TWOTOUCH_T27,
> + MXT_SPT_CTECONFIG_T28, MXT_DEBUG_DIAGNOSTIC_T37,
> + MXT_NUM_OF_OBJECT_TYPES
> +};
Personally I find it easier to read a mostly monotonically increasing enum (or
array below) when it's not in two columns. Was this copied from some Linux
header as is so that we wouldn't want to reformat it to allow for easy updates,
or was this a design choice of yours?
> +#define TYPE_MAXTOUCH "maxtouch"
> +#define MXT(obj) \
> + OBJECT_CHECK(MXTState, (obj), TYPE_MAXTOUCH)
> +#define MXT_CLASS(klass) \
> + OBJECT_CLASS_CHECK(MXTClass, (klass), TYPE_MAXTOUCH)
> +#define MXT_GET_CLASS(obj) \
> + OBJECT_GET_CLASS(MXTClass, (obj), TYPE_MAXTOUCH)
> +static const uint8_t mxt_variant0_info[MXT_INFO_SIZE] = {
> + [MXT_FAMILY_ID] = 0x80,
> + [MXT_VARIANT_ID] = 0x0,
> + [MXT_VERSION] = 0x1,
> + [MXT_BUILD] = 0x1,
> + [MXT_MATRIX_X_SIZE] = 16,
> + [MXT_MATRIX_Y_SIZE] = 14,
> + [MXT_OBJ_NUM] = sizeof(mxt_variant0_objlist) / sizeof(ObjConfig),
We have an ARRAY_SIZE() macro for this.
> +};
> +static const uint8_t mxt_variant1_info[MXT_INFO_SIZE] = {
> + [MXT_FAMILY_ID] = 0x80,
> + [MXT_VARIANT_ID] = 0x1,
> + [MXT_VERSION] = 0x1,
> + [MXT_BUILD] = 0x1,
> + [MXT_MATRIX_X_SIZE] = 16,
> + [MXT_MATRIX_Y_SIZE] = 14,
> + [MXT_OBJ_NUM] = sizeof(mxt_variant1_objlist) / sizeof(ObjConfig),
Dito.
> +};
> +
> +static const MXTVariantInfo mxt_variants_info_array[] = {
> + {
> + .name = TYPE_MXT_VARIANT0,
> + .mxt_variant_info = mxt_variant0_info,
> + .mxt_variant_obj_list = mxt_variant0_objlist
> + },
> + {
> + .name = TYPE_MXT_VARIANT1,
> + .mxt_variant_info = mxt_variant1_info,
> + .mxt_variant_obj_list = mxt_variant1_objlist
> + }
> +};
> +
> +#define MXT_NUM_OF_VARIANTS \
> + (sizeof(mxt_variants_info_array) / sizeof(MXTVariantInfo))
Dito.
> +#if MXT_DEBUG
> +#define DPRINT(fmt, args...) \
> + do {fprintf(stderr, "QEMU MXT: "fmt, ## args); } while (0)
> +#define DPRINT_SMPL(fmt, args...) \
> + do {fprintf(stderr, fmt, ## args); } while (0)
> +#define ERRPRINT(fmt, args...) \
> + do {fprintf(stderr, "QEMU MXT ERROR: "fmt, ## args); } while (0)
Please insert a space after do {. Also somewhere below.
> +static TypeInfo maxtouch_type_info = {
Can be static const.
> + .name = TYPE_MAXTOUCH,
> + .parent = TYPE_I2C_SLAVE,
> + .instance_size = sizeof(MXTState),
> + .instance_init = mxt_init,
> + .instance_finalize = mxt_finalize,
> + .abstract = true,
> + .class_size = sizeof(MXTClass),
> +};
> +
> +static void mxt_types_init(void)
Please name it mxt_register_types by convention.
> +{
> + unsigned i;
> +
> + type_register_static(&maxtouch_type_info);
> + for (i = 0; i < MXT_NUM_OF_VARIANTS; i++) {
> + mxt_register_type(&mxt_variants_info_array[i]);
> + }
> +}
> +
> +type_init(mxt_types_init)
Andreas
--
SUSE LINUX Products GmbH, Maxfeldstr. 5, 90409 Nürnberg, Germany
GF: Jeff Hawn, Jennifer Guild, Felix Imendörffer; HRB 16746 AG Nürnberg
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation Igor Mitsyanko
@ 2012-03-15 10:35 ` Andreas Färber
2012-03-21 11:55 ` Peter Maydell
2012-04-03 13:54 ` Dmitry Zhurikhin
2 siblings, 0 replies; 18+ messages in thread
From: Andreas Färber @ 2012-03-15 10:35 UTC (permalink / raw)
To: Igor Mitsyanko
Cc: peter.maydell, e.voevodin, qemu-devel, kyungmin.park, d.solodkiy,
m.kozlov
Am 15.03.2012 08:35, schrieb Igor Mitsyanko:
> Create 9 exynos4210 i2c interfaces.
>
> Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
Reviewed-by: Andreas Färber <afaerber@suse.de>
for the non-technical aspects. One remark below.
> diff --git a/hw/exynos4210_i2c.c b/hw/exynos4210_i2c.c
> new file mode 100644
> index 0000000..42f770d
> --- /dev/null
> +++ b/hw/exynos4210_i2c.c
> +enum {
> + idle,
> + start_bit_received,
> + sending_data,
> + receiving_data
> +};
Usually our enum values are uppercase, not sure if that's a rule.
Andreas
--
SUSE LINUX Products GmbH, Maxfeldstr. 5, 90409 Nürnberg, Germany
GF: Jeff Hawn, Jennifer Guild, Felix Imendörffer; HRB 16746 AG Nürnberg
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 3/3] hw: add Atmel maxtouch touchscreen implementation
2012-03-15 9:48 ` Andreas Färber
2012-03-15 10:25 ` Dmitry Solodkiy
@ 2012-03-15 10:41 ` Igor Mitsyanko
1 sibling, 0 replies; 18+ messages in thread
From: Igor Mitsyanko @ 2012-03-15 10:41 UTC (permalink / raw)
To: Andreas Färber
Cc: peter.maydell, e.voevodin, qemu-devel, kyungmin.park, d.solodkiy,
m.kozlov
On 03/15/2012 01:48 PM, Andreas Färber wrote:
> Am 15.03.2012 08:35, schrieb Igor Mitsyanko:
>> And use it for exynos4210 NURI board emulation
>>
>> Signed-off-by: Igor Mitsyanko<i.mitsyanko@samsung.com>
>
> Thanks for moving this to libhw. Looks okay, not knowing I2C or the
> device and assuming you've tested it; some nitpicks below.
>
>> diff --git a/hw/exynos4_boards.c b/hw/exynos4_boards.c
>> index b438361..f851026 100644
>> --- a/hw/exynos4_boards.c
>> +++ b/hw/exynos4_boards.c
>
>> @@ -134,9 +136,12 @@ static void nuri_init(ram_addr_t ram_size,
>> const char *kernel_filename, const char *kernel_cmdline,
>> const char *initrd_filename, const char *cpu_model)
>> {
>> - exynos4_boards_init_common(kernel_filename, kernel_cmdline,
>> - initrd_filename, EXYNOS4_BOARD_NURI);
>> -
>> + Exynos4210State *cpu = exynos4_boards_init_common(kernel_filename,
>> + kernel_cmdline, initrd_filename, EXYNOS4_BOARD_NURI);
>
> I'd advise against using "cpu" for what looks like board or SoC state to
> avoid confusion. "cpu" is going to be used for CPUState instances, i.e.
> one day: ARMCPU *cpu = cpu_arm_init("cortex-a9");
>
> I'd suggest s (generically for state) here.
Right, and also we already use s in SMDK board init function.
>> + DeviceState *dev =
>> + i2c_create_slave(cpu->i2c_if[3], "maxtouch.var0", MAXTOUCH_TS_I2C_ADDR);
>> + qdev_connect_gpio_out(dev, 0, qdev_get_gpio_in(cpu->gpio2x,
>> + EXYNOS4210_GPIO2X_LINE(GPX0, 4)));
>> arm_load_kernel(first_cpu,&exynos4_board_binfo);
>> }
>>
>> diff --git a/hw/maxtouch.c b/hw/maxtouch.c
>> new file mode 100644
>> index 0000000..0c37d30
>> --- /dev/null
>> +++ b/hw/maxtouch.c
>> @@ -0,0 +1,1079 @@
>> +/*
>> + * Atmel maXTouch touchscreen emulation
>> + *
>> + * Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved.
>
> It's been remarked that "All rights reserved." conflicts with the GPL's
> copyleft. Please drop that sentence if you can.
Ok
>> + * Igor Mitsyanko<i.mitsyanko@samsung.com>
>> + *
>> + * 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 of the License, or
>> + * (at your option) any later version.
>> + *
>> + * 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/>.
>> + *
>> + */
>
>> +/* Object types */
>> +enum {
>> + MXT_GEN_MESSAGE_T5 = 0, MXT_GEN_COMMAND_T6,
>> + MXT_GEN_POWER_T7, MXT_GEN_ACQUIRE_T8,
>> + MXT_TOUCH_MULTI_T9, MXT_TOUCH_KEYARRAY_T15,
>> + MXT_SPT_COMMSCONFIG_T18, MXT_SPT_GPIOPWM_T19,
>> + MXT_PROCI_GRIPFACE_T20, MXT_PROCG_NOISE_T22,
>> + MXT_TOUCH_PROXIMITY_T23, MXT_PROCI_ONETOUCH_T24,
>> + MXT_SPT_SELFTEST_T25, MXT_PROCI_TWOTOUCH_T27,
>> + MXT_SPT_CTECONFIG_T28, MXT_DEBUG_DIAGNOSTIC_T37,
>> + MXT_NUM_OF_OBJECT_TYPES
>> +};
>
> Personally I find it easier to read a mostly monotonically increasing
> enum (or array below) when it's not in two columns. Was this copied from
> some Linux header as is so that we wouldn't want to reformat it to allow
> for easy updates, or was this a design choice of yours?
It was my attempt to reduce amount of time needed to scroll this file
down, I don't mind reformatting it to one column.
>> +#define TYPE_MAXTOUCH "maxtouch"
>> +#define MXT(obj) \
>> + OBJECT_CHECK(MXTState, (obj), TYPE_MAXTOUCH)
>> +#define MXT_CLASS(klass) \
>> + OBJECT_CLASS_CHECK(MXTClass, (klass), TYPE_MAXTOUCH)
>> +#define MXT_GET_CLASS(obj) \
>> + OBJECT_GET_CLASS(MXTClass, (obj), TYPE_MAXTOUCH)
>
>> +static const uint8_t mxt_variant0_info[MXT_INFO_SIZE] = {
>> + [MXT_FAMILY_ID] = 0x80,
>> + [MXT_VARIANT_ID] = 0x0,
>> + [MXT_VERSION] = 0x1,
>> + [MXT_BUILD] = 0x1,
>> + [MXT_MATRIX_X_SIZE] = 16,
>> + [MXT_MATRIX_Y_SIZE] = 14,
>> + [MXT_OBJ_NUM] = sizeof(mxt_variant0_objlist) / sizeof(ObjConfig),
>
> We have an ARRAY_SIZE() macro for this.
OK
>> +};
>
>> +static const uint8_t mxt_variant1_info[MXT_INFO_SIZE] = {
>> + [MXT_FAMILY_ID] = 0x80,
>> + [MXT_VARIANT_ID] = 0x1,
>> + [MXT_VERSION] = 0x1,
>> + [MXT_BUILD] = 0x1,
>> + [MXT_MATRIX_X_SIZE] = 16,
>> + [MXT_MATRIX_Y_SIZE] = 14,
>> + [MXT_OBJ_NUM] = sizeof(mxt_variant1_objlist) / sizeof(ObjConfig),
>
> Dito.
>
>> +};
>> +
>> +static const MXTVariantInfo mxt_variants_info_array[] = {
>> + {
>> + .name = TYPE_MXT_VARIANT0,
>> + .mxt_variant_info = mxt_variant0_info,
>> + .mxt_variant_obj_list = mxt_variant0_objlist
>> + },
>> + {
>> + .name = TYPE_MXT_VARIANT1,
>> + .mxt_variant_info = mxt_variant1_info,
>> + .mxt_variant_obj_list = mxt_variant1_objlist
>> + }
>> +};
>> +
>> +#define MXT_NUM_OF_VARIANTS \
>> + (sizeof(mxt_variants_info_array) / sizeof(MXTVariantInfo))
>
> Dito.
>
>> +#if MXT_DEBUG
>> +#define DPRINT(fmt, args...) \
>> + do {fprintf(stderr, "QEMU MXT: "fmt, ## args); } while (0)
>> +#define DPRINT_SMPL(fmt, args...) \
>> + do {fprintf(stderr, fmt, ## args); } while (0)
>> +#define ERRPRINT(fmt, args...) \
>> + do {fprintf(stderr, "QEMU MXT ERROR: "fmt, ## args); } while (0)
>
> Please insert a space after do {. Also somewhere below.
>
>> +static TypeInfo maxtouch_type_info = {
>
> Can be static const.
>
Ok
>> + .name = TYPE_MAXTOUCH,
>> + .parent = TYPE_I2C_SLAVE,
>> + .instance_size = sizeof(MXTState),
>> + .instance_init = mxt_init,
>> + .instance_finalize = mxt_finalize,
>> + .abstract = true,
>> + .class_size = sizeof(MXTClass),
>> +};
>> +
>> +static void mxt_types_init(void)
>
> Please name it mxt_register_types by convention.
>
Sure
Thanks for your review!
--
Mitsyanko Igor
ASWG, Moscow R&D center, Samsung Electronics
email: i.mitsyanko@samsung.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 2/3] exynos4210: add exynos4210 GPIO implementation
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 2/3] exynos4210: add exynos4210 GPIO implementation Igor Mitsyanko
@ 2012-03-15 11:06 ` Andreas Färber
2012-03-20 13:27 ` Peter Maydell
1 sibling, 0 replies; 18+ messages in thread
From: Andreas Färber @ 2012-03-15 11:06 UTC (permalink / raw)
To: Igor Mitsyanko
Cc: peter.maydell, e.voevodin, qemu-devel, kyungmin.park, d.solodkiy,
m.kozlov
Am 15.03.2012 08:35, schrieb Igor Mitsyanko:
> Now that we have GPIO emulation for exynos4210 SoC we can use it to
> properly hook up IRQ line to lan9215 controller on SMDK board.
>
> Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
The patches for .instance_size were applied now, so the TypeInfos look
okay, although a bit confusing at first. ;) Same for .class_size but
type_initialize() seems to handle that correctly.
Reviewed-by: Andreas Färber <afaerber@suse.de>
All static TypeInfos could be made const for a v3.
Series looks plausible; Peter should take a second, closer look though.
Probably best to wait for more comments before resending minor fixes.
Thanks,
Andreas
--
SUSE LINUX Products GmbH, Maxfeldstr. 5, 90409 Nürnberg, Germany
GF: Jeff Hawn, Jennifer Guild, Felix Imendörffer; HRB 16746 AG Nürnberg
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 2/3] exynos4210: add exynos4210 GPIO implementation
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 2/3] exynos4210: add exynos4210 GPIO implementation Igor Mitsyanko
2012-03-15 11:06 ` Andreas Färber
@ 2012-03-20 13:27 ` Peter Maydell
2012-03-21 13:01 ` Igor Mitsyanko
1 sibling, 1 reply; 18+ messages in thread
From: Peter Maydell @ 2012-03-20 13:27 UTC (permalink / raw)
To: Igor Mitsyanko
Cc: e.voevodin, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov,
afaerber
On 15 March 2012 07:35, Igor Mitsyanko <i.mitsyanko@samsung.com> wrote:
> Now that we have GPIO emulation for exynos4210 SoC we can use it to
> properly hook up IRQ line to lan9215 controller on SMDK board.
> +#elif EXYNOS4210_GPIO_DEBUG == 1
> +#define DPRINT_L1(fmt, args...) \
> + do {fprintf(stderr, "QEMU GPIO: "fmt, ## args); } while (0)
Space after the closing quote in these macros (this patch and
others), please.
> +
> + DPRINT_L1("Input pin GPIO%s_PIN%u %s by external device\n",
> + g->ports[group_num].name, pin, (level ? "set" : "cleared"));
> + /* Set new value on corresponding gpio pin */
> + (level) ? (g->ports[group_num].dat |= (1 << pin)) :
> + (g->ports[group_num].dat &= ~(1 << pin));
Don't use ?: like this please, just write it out as an if statement.
> +static void exynos4_gpio_write(void *opaque, target_phys_addr_t off,
> + uint64_t value, unsigned size)
> +{
I find these read and write functions quite hard to read. I'm
wondering if it would work better to split them up into smaller
memory regions which get registered at the right addresses, rather
than effectively doing the decode into different subsections based
on address and g->part here?
> + Exynos4GpioState *g = (Exynos4GpioState *)opaque;
> + unsigned int port_end, extint_con_start, extint_con_end;
> + unsigned int extint_flt_start, extint_flt_end;
> + unsigned int extint_mask_start, extint_mask_end;
> + unsigned int extint_pend_start, extint_pend_end;
> + unsigned int etcp_start_addr, etcp_start_idx, extint_pri_end;
> + unsigned idx;
> +
> + DPRINT_L2("GPIO%u write off 0x%x = %u(0x%x)\n", g->part,
> + (uint32_t)off, (uint32_t)value, (uint32_t)value);
> +
> + switch (g->part) {
> + case GPIO_PART2X:
> + port_end = GPIO2_XPORT_END;
> + extint_con_start = GPIO2_WKPINT_CON_START;
> + extint_con_end = GPIO2_WKPINT_CON_END;
> + extint_flt_start = GPIO2_WKPINT_FLT_START;
> + extint_flt_end = GPIO2_WKPINT_FLT_END;
> + extint_mask_start = GPIO2_WKPINT_MASK_START;
> + extint_mask_end = GPIO2_WKPINT_MASK_END;
> + extint_pend_start = GPIO2_WKPINT_PEND_START;
> + extint_pend_end = GPIO2_WKPINT_PEND_END;
> + etcp_start_addr = etcp_start_idx = extint_pri_end = 0;
> + break;
> + case GPIO_PART1: default:
> + port_end = GPIO_NORM_PORT_END;
> + extint_con_start = GPIO_EXTINT_CON_START;
> + extint_con_end = GPIO1_EXTINT_CON_END;
> + extint_flt_start = GPIO_EXTINT_FLT_START;
> + extint_flt_end = GPIO1_EXTINT_FLT_END;
> + extint_mask_start = GPIO_EXTINT_MASK_START;
> + extint_mask_end = GPIO1_EXTINT_MASK_END;
> + extint_pend_start = GPIO_EXTINT_PEND_START;
> + extint_pend_end = GPIO1_EXTINT_PEND_END;
> + etcp_start_addr = GPIO1_ETCPORT_START;
> + etcp_start_idx = GPIO1_NORM_PORT_NUM;
> + extint_pri_end = GPIO1_EXTINT_FIXPRI_END;
> + break;
> + case GPIO_PART2:
> + port_end = GPIO_NORM_PORT_END;
> + extint_con_start = GPIO_EXTINT_CON_START;
> + extint_con_end = GPIO2_EXTINT_CON_END;
> + extint_flt_start = GPIO_EXTINT_FLT_START;
> + extint_flt_end = GPIO2_EXTINT_FLT_END;
> + extint_mask_start = GPIO_EXTINT_MASK_START;
> + extint_mask_end = GPIO2_EXTINT_MASK_END;
> + extint_pend_start = GPIO_EXTINT_PEND_START;
> + extint_pend_end = GPIO2_EXTINT_PEND_END;
> + etcp_start_addr = GPIO2_ETCPORT_START;
> + etcp_start_idx = GPIO2_NORM_PORT_NUM;
> + extint_pri_end = GPIO2_EXTINT_FIXPRI_END;
> + break;
> + case GPIO_PART3:
> + if (off < GPIO3_NORM_PORT_END) {
> + idx = DIV_BY_PORTGR_SIZE(off);
> + exynos4_gpio_portgr_write(g, idx, MOD_PORTGR_SIZE(off), value);
> + } else {
> + DPRINT_ERROR("GPIO3 bad write off 0x%x = %u(0x%x)\n",
> + (uint32_t)off, (uint32_t)value, (uint32_t)value);
> + }
> + return;
> + }
> +
> + if (off < port_end) {
> + idx = DIV_BY_PORTGR_SIZE(off);
> + exynos4_gpio_portgr_write(g, idx, MOD_PORTGR_SIZE(off), value);
> + } else if (off >= extint_mask_start && off < extint_mask_end) {
> + idx = (off - extint_mask_start) >> 2;
> + DPRINT_L1("GPIO%u EXTINT%u_MASK register write = %u(0x%x)\n", g->part,
> + g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
> + g->port_ints[idx].mask = value;
> + } else if (off >= extint_pend_start && off < extint_pend_end) {
> + idx = (off - extint_pend_start) >> 2;
> + DPRINT_L1("GPIO%u EXTINT%u_PEND register write = %u(0x%x)\n", g->part,
> + g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
> + if (g->part == GPIO_PART2X) {
> + unsigned i, irq_n;
> + Exynos4Gpio2XState *g2 = EXYNOS4210_GPIO2X(g);
> + for (i = 0; i < GPIO_MAX_PIN_IN_PORT; i++) {
> + if ((g->port_ints[idx].pend & (1 << i)) && (value & (1 << i))) {
> + g->port_ints[idx].pend &= ~(1 << i);
> + irq_n = idx * GPIO_MAX_PIN_IN_PORT + i;
> + if (irq_n >= GPIO2_X_PORT_IRQ_NUM) {
> + irq_n = GPIO2_X_PORT_IRQ_NUM - 1;
> + }
> + qemu_irq_lower(g2->ext_irq[irq_n]);
> + }
> + }
> + } else {
> + g->port_ints[idx].pend &= ~value;
> + exynos4_gpioirq_update(g);
> + }
> + } else if (off >= extint_con_start && off < extint_con_end) {
> + idx = (off - extint_con_start) >> 2;
> + DPRINT_L1("GPIO%u EXTINT%u_CON register write = %u(0x%x)\n", g->part,
> + g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
> + g->port_ints[idx].con = value;
> + } else if (off >= extint_flt_start && off < extint_flt_end) {
> + unsigned i = ((off - extint_flt_start) >> 2) & 1;
> + idx = (off - extint_flt_start) >> 3;
> + DPRINT_L1("GPIO%u EXTINT%u_FLTCON%u reg write = %u(0x%x)\n", g->part,
> + g->port_ints[idx].int_line_num, i, (uint32_t)value, (uint32_t)value);
> + g->port_ints[idx].fltcon[i] = value;
> + } else if (g->part == GPIO_PART2X) {
> + DPRINT_ERROR("GPIO2 group X bad write off 0x%x = %u(0x%x)\n",
> + (uint32_t)off, (uint32_t)value, (uint32_t)value);
> + return;
> + } else if (off == GPIO_EXTINT_SERVICE) {
> + DPRINT_L1("GPIO%u EXT_INT_SERVICE register write = %u(0x%x)\n",
> + g->part, (uint32_t)value, (uint32_t)value);
> + EXYNOS4210_GPIO_1_2(g)->extint_serv = value;
> + } else if (off == GPIO_EXTINT_SERVICE_PEND) {
> + DPRINT_L1("GPIO%u EXT_INT_SERVICE_PEND register write = %u(0x%x)\n",
> + g->part, (uint32_t)value, (uint32_t)value);
> + EXYNOS4210_GPIO_1_2(g)->extint_serv_pend = value;
> + } else if (off == GPIO_EXTINT_GRPFIXPRI) {
> + DPRINT_L1("GPIO%u EXT_INT_GRPFIXPRI register write = %u(0x%x)\n",
> + g->part, (uint32_t)value, (uint32_t)value);
> + EXYNOS4210_GPIO_1_2(g)->extint_grpfixpri = value;
> + } else if (off >= GPIO_EXTINT_FIXPRI_START && off < extint_pri_end) {
> + idx = (off - GPIO_EXTINT_FIXPRI_START) >> 2;
> + DPRINT_L1("GPIO%u EXTINT%u_FIXPRI register write = %u(0x%x)\n", g->part,
> + g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
> + g->port_ints[idx].fixpri = value;
> + } else if (off >= etcp_start_addr && off < GPIO_ETCPORT_END) {
> + idx = etcp_start_idx + DIV_BY_PORTGR_SIZE(off - etcp_start_addr);
> + exynos4_etc_portgroup_write(&g->ports[idx],
> + MOD_PORTGR_SIZE(off - etcp_start_addr), value);
> + } else {
> + DPRINT_ERROR("GPIO%u bad write off 0x%x = %u(0x%x)\n", g->part,
> + (uint32_t)off, (uint32_t)value, (uint32_t)value);
> + }
> +}
> +static void exynos4_gpio1_init(Object *obj)
> +{
> + Exynos4GpioState *g = EXYNOS4210_GPIO(obj);
> +
> + g->part = GPIO_PART1;
> + g->ports = gpio1_ports;
> + g->port_ints = gpio1_ports_interrupts;
> + g->num_of_ports = sizeof(gpio1_ports) / sizeof(Exynos4PortGroup);
> + g->num_of_portints =
> + sizeof(gpio1_ports_interrupts) / sizeof(Exynos4PortIntState);
You can use the ARRAY_SIZE macro here.
-- PMM
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation Igor Mitsyanko
2012-03-15 10:35 ` Andreas Färber
@ 2012-03-21 11:55 ` Peter Maydell
2012-03-21 13:07 ` Igor Mitsyanko
2012-04-03 13:54 ` Dmitry Zhurikhin
2 siblings, 1 reply; 18+ messages in thread
From: Peter Maydell @ 2012-03-21 11:55 UTC (permalink / raw)
To: Igor Mitsyanko
Cc: e.voevodin, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov,
afaerber
On 15 March 2012 07:35, Igor Mitsyanko <i.mitsyanko@samsung.com> wrote:
> Create 9 exynos4210 i2c interfaces.
>
> Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
Mostly this looks OK but I still find the i2c slave stuff
odd -- should the controller really register itself as
a slave on its own bus? Doesn't this mean that the
controller can effectively try to talk to itself? Does
the hardware let you do that?
I suspect that what's happening here is that the hardware
lets you put the i2c controller into slave mode so some
other device on the bus can be a master. But QEMU's
i2c bus abstraction doesn't cover that use case at all...
Anyway, I don't know enough about i2c to really be able
to review this patch properly in that area. Anybody?
-- PMM
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 2/3] exynos4210: add exynos4210 GPIO implementation
2012-03-20 13:27 ` Peter Maydell
@ 2012-03-21 13:01 ` Igor Mitsyanko
0 siblings, 0 replies; 18+ messages in thread
From: Igor Mitsyanko @ 2012-03-21 13:01 UTC (permalink / raw)
To: Peter Maydell; +Cc: qemu-devel, afaerber, d.solodkiy
On 03/20/2012 05:27 PM, Peter Maydell wrote:
> On 15 March 2012 07:35, Igor Mitsyanko<i.mitsyanko@samsung.com> wrote:
>> Now that we have GPIO emulation for exynos4210 SoC we can use it to
>> properly hook up IRQ line to lan9215 controller on SMDK board.
>
>> +#elif EXYNOS4210_GPIO_DEBUG == 1
>> +#define DPRINT_L1(fmt, args...) \
>> + do {fprintf(stderr, "QEMU GPIO: "fmt, ## args); } while (0)
>
> Space after the closing quote in these macros (this patch and
> others), please.
>
>> +
>> + DPRINT_L1("Input pin GPIO%s_PIN%u %s by external device\n",
>> + g->ports[group_num].name, pin, (level ? "set" : "cleared"));
>> + /* Set new value on corresponding gpio pin */
>> + (level) ? (g->ports[group_num].dat |= (1<< pin)) :
>> + (g->ports[group_num].dat&= ~(1<< pin));
>
> Don't use ?: like this please, just write it out as an if statement.
>
OK
>> +static void exynos4_gpio_write(void *opaque, target_phys_addr_t off,
>> + uint64_t value, unsigned size)
>> +{
>
> I find these read and write functions quite hard to read. I'm
> wondering if it would work better to split them up into smaller
> memory regions which get registered at the right addresses, rather
> than effectively doing the decode into different subsections based
> on address and g->part here?
>
Initially I implemented these functions splitted like you suggest, but
that left me with three basically identical long switch statements, so I
decided to combine them into one to achieve better code reuse. If you
think code reuse is not an issue here, I'll gladly return it back to
splitted implementation.
>> + Exynos4GpioState *g = (Exynos4GpioState *)opaque;
>> + unsigned int port_end, extint_con_start, extint_con_end;
>> + unsigned int extint_flt_start, extint_flt_end;
>> + unsigned int extint_mask_start, extint_mask_end;
>> + unsigned int extint_pend_start, extint_pend_end;
>> + unsigned int etcp_start_addr, etcp_start_idx, extint_pri_end;
>> + unsigned idx;
>> +
>> + DPRINT_L2("GPIO%u write off 0x%x = %u(0x%x)\n", g->part,
>> + (uint32_t)off, (uint32_t)value, (uint32_t)value);
>> +
>> + switch (g->part) {
>> + case GPIO_PART2X:
>> + port_end = GPIO2_XPORT_END;
>> + extint_con_start = GPIO2_WKPINT_CON_START;
>> + extint_con_end = GPIO2_WKPINT_CON_END;
>> + extint_flt_start = GPIO2_WKPINT_FLT_START;
>> + extint_flt_end = GPIO2_WKPINT_FLT_END;
>> + extint_mask_start = GPIO2_WKPINT_MASK_START;
>> + extint_mask_end = GPIO2_WKPINT_MASK_END;
>> + extint_pend_start = GPIO2_WKPINT_PEND_START;
>> + extint_pend_end = GPIO2_WKPINT_PEND_END;
>> + etcp_start_addr = etcp_start_idx = extint_pri_end = 0;
>> + break;
>> + case GPIO_PART1: default:
>> + port_end = GPIO_NORM_PORT_END;
>> + extint_con_start = GPIO_EXTINT_CON_START;
>> + extint_con_end = GPIO1_EXTINT_CON_END;
>> + extint_flt_start = GPIO_EXTINT_FLT_START;
>> + extint_flt_end = GPIO1_EXTINT_FLT_END;
>> + extint_mask_start = GPIO_EXTINT_MASK_START;
>> + extint_mask_end = GPIO1_EXTINT_MASK_END;
>> + extint_pend_start = GPIO_EXTINT_PEND_START;
>> + extint_pend_end = GPIO1_EXTINT_PEND_END;
>> + etcp_start_addr = GPIO1_ETCPORT_START;
>> + etcp_start_idx = GPIO1_NORM_PORT_NUM;
>> + extint_pri_end = GPIO1_EXTINT_FIXPRI_END;
>> + break;
>> + case GPIO_PART2:
>> + port_end = GPIO_NORM_PORT_END;
>> + extint_con_start = GPIO_EXTINT_CON_START;
>> + extint_con_end = GPIO2_EXTINT_CON_END;
>> + extint_flt_start = GPIO_EXTINT_FLT_START;
>> + extint_flt_end = GPIO2_EXTINT_FLT_END;
>> + extint_mask_start = GPIO_EXTINT_MASK_START;
>> + extint_mask_end = GPIO2_EXTINT_MASK_END;
>> + extint_pend_start = GPIO_EXTINT_PEND_START;
>> + extint_pend_end = GPIO2_EXTINT_PEND_END;
>> + etcp_start_addr = GPIO2_ETCPORT_START;
>> + etcp_start_idx = GPIO2_NORM_PORT_NUM;
>> + extint_pri_end = GPIO2_EXTINT_FIXPRI_END;
>> + break;
>> + case GPIO_PART3:
>> + if (off< GPIO3_NORM_PORT_END) {
>> + idx = DIV_BY_PORTGR_SIZE(off);
>> + exynos4_gpio_portgr_write(g, idx, MOD_PORTGR_SIZE(off), value);
>> + } else {
>> + DPRINT_ERROR("GPIO3 bad write off 0x%x = %u(0x%x)\n",
>> + (uint32_t)off, (uint32_t)value, (uint32_t)value);
>> + }
>> + return;
>> + }
>> +
>> + if (off< port_end) {
>> + idx = DIV_BY_PORTGR_SIZE(off);
>> + exynos4_gpio_portgr_write(g, idx, MOD_PORTGR_SIZE(off), value);
>> + } else if (off>= extint_mask_start&& off< extint_mask_end) {
>> + idx = (off - extint_mask_start)>> 2;
>> + DPRINT_L1("GPIO%u EXTINT%u_MASK register write = %u(0x%x)\n", g->part,
>> + g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
>> + g->port_ints[idx].mask = value;
>> + } else if (off>= extint_pend_start&& off< extint_pend_end) {
>> + idx = (off - extint_pend_start)>> 2;
>> + DPRINT_L1("GPIO%u EXTINT%u_PEND register write = %u(0x%x)\n", g->part,
>> + g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
>> + if (g->part == GPIO_PART2X) {
>> + unsigned i, irq_n;
>> + Exynos4Gpio2XState *g2 = EXYNOS4210_GPIO2X(g);
>> + for (i = 0; i< GPIO_MAX_PIN_IN_PORT; i++) {
>> + if ((g->port_ints[idx].pend& (1<< i))&& (value& (1<< i))) {
>> + g->port_ints[idx].pend&= ~(1<< i);
>> + irq_n = idx * GPIO_MAX_PIN_IN_PORT + i;
>> + if (irq_n>= GPIO2_X_PORT_IRQ_NUM) {
>> + irq_n = GPIO2_X_PORT_IRQ_NUM - 1;
>> + }
>> + qemu_irq_lower(g2->ext_irq[irq_n]);
>> + }
>> + }
>> + } else {
>> + g->port_ints[idx].pend&= ~value;
>> + exynos4_gpioirq_update(g);
>> + }
>> + } else if (off>= extint_con_start&& off< extint_con_end) {
>> + idx = (off - extint_con_start)>> 2;
>> + DPRINT_L1("GPIO%u EXTINT%u_CON register write = %u(0x%x)\n", g->part,
>> + g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
>> + g->port_ints[idx].con = value;
>> + } else if (off>= extint_flt_start&& off< extint_flt_end) {
>> + unsigned i = ((off - extint_flt_start)>> 2)& 1;
>> + idx = (off - extint_flt_start)>> 3;
>> + DPRINT_L1("GPIO%u EXTINT%u_FLTCON%u reg write = %u(0x%x)\n", g->part,
>> + g->port_ints[idx].int_line_num, i, (uint32_t)value, (uint32_t)value);
>> + g->port_ints[idx].fltcon[i] = value;
>> + } else if (g->part == GPIO_PART2X) {
>> + DPRINT_ERROR("GPIO2 group X bad write off 0x%x = %u(0x%x)\n",
>> + (uint32_t)off, (uint32_t)value, (uint32_t)value);
>> + return;
>> + } else if (off == GPIO_EXTINT_SERVICE) {
>> + DPRINT_L1("GPIO%u EXT_INT_SERVICE register write = %u(0x%x)\n",
>> + g->part, (uint32_t)value, (uint32_t)value);
>> + EXYNOS4210_GPIO_1_2(g)->extint_serv = value;
>> + } else if (off == GPIO_EXTINT_SERVICE_PEND) {
>> + DPRINT_L1("GPIO%u EXT_INT_SERVICE_PEND register write = %u(0x%x)\n",
>> + g->part, (uint32_t)value, (uint32_t)value);
>> + EXYNOS4210_GPIO_1_2(g)->extint_serv_pend = value;
>> + } else if (off == GPIO_EXTINT_GRPFIXPRI) {
>> + DPRINT_L1("GPIO%u EXT_INT_GRPFIXPRI register write = %u(0x%x)\n",
>> + g->part, (uint32_t)value, (uint32_t)value);
>> + EXYNOS4210_GPIO_1_2(g)->extint_grpfixpri = value;
>> + } else if (off>= GPIO_EXTINT_FIXPRI_START&& off< extint_pri_end) {
>> + idx = (off - GPIO_EXTINT_FIXPRI_START)>> 2;
>> + DPRINT_L1("GPIO%u EXTINT%u_FIXPRI register write = %u(0x%x)\n", g->part,
>> + g->port_ints[idx].int_line_num, (uint32_t)value, (uint32_t)value);
>> + g->port_ints[idx].fixpri = value;
>> + } else if (off>= etcp_start_addr&& off< GPIO_ETCPORT_END) {
>> + idx = etcp_start_idx + DIV_BY_PORTGR_SIZE(off - etcp_start_addr);
>> + exynos4_etc_portgroup_write(&g->ports[idx],
>> + MOD_PORTGR_SIZE(off - etcp_start_addr), value);
>> + } else {
>> + DPRINT_ERROR("GPIO%u bad write off 0x%x = %u(0x%x)\n", g->part,
>> + (uint32_t)off, (uint32_t)value, (uint32_t)value);
>> + }
>> +}
>> +static void exynos4_gpio1_init(Object *obj)
>> +{
>> + Exynos4GpioState *g = EXYNOS4210_GPIO(obj);
>> +
>> + g->part = GPIO_PART1;
>> + g->ports = gpio1_ports;
>> + g->port_ints = gpio1_ports_interrupts;
>> + g->num_of_ports = sizeof(gpio1_ports) / sizeof(Exynos4PortGroup);
>> + g->num_of_portints =
>> + sizeof(gpio1_ports_interrupts) / sizeof(Exynos4PortIntState);
>
> You can use the ARRAY_SIZE macro here.
>
> -- PMM
>
>
--
Mitsyanko Igor
ASWG, Moscow R&D center, Samsung Electronics
email: i.mitsyanko@samsung.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation
2012-03-21 11:55 ` Peter Maydell
@ 2012-03-21 13:07 ` Igor Mitsyanko
2012-03-21 13:09 ` Peter Maydell
0 siblings, 1 reply; 18+ messages in thread
From: Igor Mitsyanko @ 2012-03-21 13:07 UTC (permalink / raw)
To: Peter Maydell
Cc: e.voevodin, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov,
afaerber
On 03/21/2012 03:55 PM, Peter Maydell wrote:
> On 15 March 2012 07:35, Igor Mitsyanko<i.mitsyanko@samsung.com> wrote:
>> Create 9 exynos4210 i2c interfaces.
>>
>> Signed-off-by: Igor Mitsyanko<i.mitsyanko@samsung.com>
>
> Mostly this looks OK but I still find the i2c slave stuff
> odd -- should the controller really register itself as
> a slave on its own bus? Doesn't this mean that the
> controller can effectively try to talk to itself? Does
> the hardware let you do that?
>
Controller's master and slave i2c interfaces operate on the same single
bus, so I think it should. It can't talk to itself because controller's
two modes of operation are mutually exclusive. As I said, I took this
approach from pxa i2c implementation, the only difference is that they
register slave interface on a separate bus, but I think it's not right
to do that.
> I suspect that what's happening here is that the hardware
> lets you put the i2c controller into slave mode so some
> other device on the bus can be a master. But QEMU's
> i2c bus abstraction doesn't cover that use case at all...
>
Yes, I saw this statement in hw/i2c.h (and probably cpu i2c controller
will never be used as i2c slave device by anyone), but I think we still
have to implement devices exactly like they described in documentation.
--
Mitsyanko Igor
ASWG, Moscow R&D center, Samsung Electronics
email: i.mitsyanko@samsung.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation
2012-03-21 13:07 ` Igor Mitsyanko
@ 2012-03-21 13:09 ` Peter Maydell
2012-03-21 14:18 ` Igor Mitsyanko
0 siblings, 1 reply; 18+ messages in thread
From: Peter Maydell @ 2012-03-21 13:09 UTC (permalink / raw)
To: i.mitsyanko
Cc: e.voevodin, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov,
afaerber
On 21 March 2012 13:07, Igor Mitsyanko <i.mitsyanko@samsung.com> wrote:
> On 03/21/2012 03:55 PM, Peter Maydell wrote:
>> I suspect that what's happening here is that the hardware
>> lets you put the i2c controller into slave mode so some
>> other device on the bus can be a master. But QEMU's
>> i2c bus abstraction doesn't cover that use case at all...
> Yes, I saw this statement in hw/i2c.h (and probably cpu i2c controller will
> never be used as i2c slave device by anyone), but I think we still have to
> implement devices exactly like they described in documentation.
I agree with the sentiment, I'm just not sure if the code you've
written is actually doing that. The right way to model this would
be if our i2c bus implementation provided an interface so you
could register as a device which is a master but can switch into
slave mode. Failing that, maybe we should just not support switching
into slave mode at all. Registering as two separate devices on the
i2c bus doesn't sound right to me.
-- PMM
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation
2012-03-21 13:09 ` Peter Maydell
@ 2012-03-21 14:18 ` Igor Mitsyanko
2012-03-21 14:24 ` Peter Maydell
0 siblings, 1 reply; 18+ messages in thread
From: Igor Mitsyanko @ 2012-03-21 14:18 UTC (permalink / raw)
To: Peter Maydell
Cc: e.voevodin, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov,
afaerber
On 03/21/2012 05:09 PM, Peter Maydell wrote:
> On 21 March 2012 13:07, Igor Mitsyanko<i.mitsyanko@samsung.com> wrote:
>> On 03/21/2012 03:55 PM, Peter Maydell wrote:
>>> I suspect that what's happening here is that the hardware
>>> lets you put the i2c controller into slave mode so some
>>> other device on the bus can be a master. But QEMU's
>>> i2c bus abstraction doesn't cover that use case at all...
>
>> Yes, I saw this statement in hw/i2c.h (and probably cpu i2c controller will
>> never be used as i2c slave device by anyone), but I think we still have to
>> implement devices exactly like they described in documentation.
>
> I agree with the sentiment, I'm just not sure if the code you've
> written is actually doing that. The right way to model this would
> be if our i2c bus implementation provided an interface so you
> could register as a device which is a master but can switch into
> slave mode.
I don't think we can do that without multiple inheritance, and it
wouldn't worth an effort for a feature that never going to be used.
Failing that, maybe we should just not support switching
> into slave mode at all.
Do you mean we shouldn't register EXYNOS4_I2C_SLAVE at all so some
hypothetical bus master wouldn't even find EXYNOS4_I2C_SLAVE on a bus?
Maybe the best solution is to make exynos4210_i2c_slave_send() and
exynos4210_i2c_slave_recv() always return -1, so a hypothetical bus
master will treat EXYNOS4_I2C_SLAVE as a broken device. But that seems
to behave exactly like "not register at all" approach..
And are we really sure that slave interface wouldn't work correctly in a
current implementation? For example, emulated Exynos CPU issues some
command to a device A on SPI line and device A in turn issues data on
i2c line connected to Exynos i2c controller configured as slave.
EXYNOS4_I2C_SLAVE receives a data and raises interrupt flag.
Registering as two separate devices on the
> i2c bus doesn't sound right to me.
>
Why? That's how it's done in hardware, I think we can roughly consider
it to be two separate devices multiplexing single bus.
--
Mitsyanko Igor
ASWG, Moscow R&D center, Samsung Electronics
email: i.mitsyanko@samsung.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation
2012-03-21 14:18 ` Igor Mitsyanko
@ 2012-03-21 14:24 ` Peter Maydell
0 siblings, 0 replies; 18+ messages in thread
From: Peter Maydell @ 2012-03-21 14:24 UTC (permalink / raw)
To: i.mitsyanko
Cc: e.voevodin, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov,
afaerber
On 21 March 2012 14:18, Igor Mitsyanko <i.mitsyanko@samsung.com> wrote:
> Do you mean we shouldn't register EXYNOS4_I2C_SLAVE at all so some
> hypothetical bus master wouldn't even find EXYNOS4_I2C_SLAVE on a bus?
> Maybe the best solution is to make exynos4210_i2c_slave_send() and
> exynos4210_i2c_slave_recv() always return -1, so a hypothetical bus master
> will treat EXYNOS4_I2C_SLAVE as a broken device. But that seems to behave
> exactly like "not register at all" approach..
> And are we really sure that slave interface wouldn't work correctly in a
> current implementation? For example, emulated Exynos CPU issues some command
> to a device A on SPI line and device A in turn issues data on i2c line
> connected to Exynos i2c controller configured as slave. EXYNOS4_I2C_SLAVE
> receives a data and raises interrupt flag.
If there's a valid configuration that works in the existing code
where we can end up receiving data correctly to the EXYNOS4_I2C_SLAVE
from some other device on the i2c bus, that's fine: we can test that
the code you have works OK.
If there is no valid configuration that will do that (because we
don't have any support for any other device being a bus master)
then the code is completely useless, untested and untestable and
we shouldn't put it in.
-- PMM
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation Igor Mitsyanko
2012-03-15 10:35 ` Andreas Färber
2012-03-21 11:55 ` Peter Maydell
@ 2012-04-03 13:54 ` Dmitry Zhurikhin
2012-04-03 14:54 ` Igor Mitsyanko
2 siblings, 1 reply; 18+ messages in thread
From: Dmitry Zhurikhin @ 2012-04-03 13:54 UTC (permalink / raw)
To: Igor Mitsyanko
Cc: peter.maydell, e.voevodin, qemu-devel, kyungmin.park, d.solodkiy,
m.kozlov, afaerber
Let me add my two cents here. I tested this patch slightly and didn't
find any problems.
On 2012-03-15 11:35, Igor Mitsyanko wrote:
> Create 9 exynos4210 i2c interfaces.
>
> Signed-off-by: Igor Mitsyanko <i.mitsyanko@samsung.com>
> ---
> Makefile.target | 1 +
> hw/exynos4210.c | 26 +++
> hw/exynos4210.h | 3 +
> hw/exynos4210_i2c.c | 469 +++++++++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 499 insertions(+), 0 deletions(-)
> create mode 100644 hw/exynos4210_i2c.c
>
> diff --git a/Makefile.target b/Makefile.target
> index eb25941..5e8a1d4 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -357,6 +357,7 @@ obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
> obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
> obj-arm-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
> obj-arm-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o
> +obj-arm-y += exynos4210_i2c.o
> obj-arm-y += arm_l2x0.o
> obj-arm-y += arm_mptimer.o a15mpcore.o
> obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
> diff --git a/hw/exynos4210.c b/hw/exynos4210.c
> index f904370..464f157 100644
> --- a/hw/exynos4210.c
> +++ b/hw/exynos4210.c
> @@ -35,6 +35,13 @@
> /* MCT */
> #define EXYNOS4210_MCT_BASE_ADDR 0x10050000
>
> +/* I2C */
> +#define EXYNOS4210_I2C_SHIFT 0x00010000
> +#define EXYNOS4210_I2C_BASE_ADDR 0x13860000
> +/* Interrupt Group of External Interrupt Combiner for I2C */
> +#define EXYNOS4210_I2C_INTG 27
> +#define EXYNOS4210_HDMI_INTG 16
> +
> /* UART's definitions */
> #define EXYNOS4210_UART0_BASE_ADDR 0x13800000
> #define EXYNOS4210_UART1_BASE_ADDR 0x13810000
> @@ -242,6 +249,25 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
> s->irq_table[exynos4210_get_irq(35, 3)]);
> sysbus_mmio_map(busdev, 0, EXYNOS4210_MCT_BASE_ADDR);
>
> + /*** I2C ***/
> + for (n = 0; n < EXYNOS4210_I2C_NUMBER; n++) {
> + uint32_t addr = EXYNOS4210_I2C_BASE_ADDR + EXYNOS4210_I2C_SHIFT * n;
> + qemu_irq i2c_irq;
> +
> + if (n < 8) {
> + i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_I2C_INTG, n)];
> + } else {
> + i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_HDMI_INTG, 1)];
> + }
> +
> + dev = qdev_create(NULL, "exynos4210.i2c");
> + qdev_init_nofail(dev);
> + busdev = sysbus_from_qdev(dev);
> + sysbus_connect_irq(busdev, 0, i2c_irq);
> + sysbus_mmio_map(busdev, 0, addr);
> + s->i2c_if[n] = (i2c_bus *)qdev_get_child_bus(dev, "i2c");
> + }
> +
> /*** UARTs ***/
> exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR,
> EXYNOS4210_UART0_FIFO_SIZE, 0, NULL,
> diff --git a/hw/exynos4210.h b/hw/exynos4210.h
> index c112e03..1205fb5 100644
> --- a/hw/exynos4210.h
> +++ b/hw/exynos4210.h
> @@ -74,6 +74,8 @@
> #define EXYNOS4210_EXT_GIC_NIRQ (160-32)
> #define EXYNOS4210_INT_GIC_NIRQ 64
>
> +#define EXYNOS4210_I2C_NUMBER 9
> +
> typedef struct Exynos4210Irq {
> qemu_irq int_combiner_irq[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ];
> qemu_irq ext_combiner_irq[EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ];
> @@ -95,6 +97,7 @@ typedef struct Exynos4210State {
> MemoryRegion dram1_mem;
> MemoryRegion boot_secondary;
> MemoryRegion bootreg_mem;
> + i2c_bus *i2c_if[EXYNOS4210_I2C_NUMBER];
> } Exynos4210State;
>
> Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
> diff --git a/hw/exynos4210_i2c.c b/hw/exynos4210_i2c.c
> new file mode 100644
> index 0000000..42f770d
> --- /dev/null
> +++ b/hw/exynos4210_i2c.c
> @@ -0,0 +1,469 @@
> +/*
> + * Exynos4210 I2C Bus Serial Interface Emulation
> + *
> + * Copyright (C) 2012 Samsung Electronics Co Ltd.
> + * Maksim Kozlov, <m.kozlov@samsung.com>
> + * Igor Mitsyanko, <i.mitsyanko@samsung.com>
> + *
> + * 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 of the License, or
> + * (at your option) any later version.
> + *
> + * 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-timer.h"
> +#include "sysbus.h"
> +#include "i2c.h"
> +
> +#ifndef EXYNOS4_I2C_DEBUG
> +#define EXYNOS4_I2C_DEBUG 0
> +#endif
> +
> +#define TYPE_EXYNOS4_I2C "exynos4210.i2c"
> +#define TYPE_EXYNOS4_I2C_SLAVE "exynos4210.i2c-slave"
> +#define EXYNOS4_I2C(obj) \
> + OBJECT_CHECK(Exynos4210I2CState, (obj), TYPE_EXYNOS4_I2C)
> +#define EXYNOS4_I2C_SLAVE(obj) \
> + OBJECT_CHECK(Exynos4210I2CSlave, (obj), TYPE_EXYNOS4_I2C_SLAVE)
> +
> +/* Exynos4210 I2C memory map */
> +#define EXYNOS4_I2C_MEM_SIZE 0x14
> +#define I2CCON_ADDR 0x00 /* control register */
> +#define I2CSTAT_ADDR 0x04 /* control/status register */
> +#define I2CADD_ADDR 0x08 /* address register */
> +#define I2CDS_ADDR 0x0c /* data shift register */
> +#define I2CLC_ADDR 0x10 /* line control register */
> +
> +#define I2CCON_INTERRUPTS_EN (1 << 5)
> +#define I2CCON_INTERRUPT_PEND (1 << 4)
> +
> +#define EXYNOS4_I2C_MODE(reg) (((reg) >> 6) & 3)
> +#define I2C_IN_MASTER_MODE(reg) (((reg) >> 6) & 2)
> +#define I2CSTAT_MODE_SLAVE_Rx 0x0
> +#define I2CSTAT_MODE_SLAVE_Tx 0x1
> +#define I2CMODE_MASTER_Rx 0x2
> +#define I2CMODE_MASTER_Tx 0x3
> +#define I2CSTAT_LAST_BIT (1 << 0)
> +#define I2CSTAT_SLAVE_ADDR_ZERO (1 << 1)
> +#define I2CSTAT_SLAVE_ADDR_MATCH (1 << 2)
> +#define I2CSTAT_ARBITR_STATUS (1 << 3)
> +#define I2CSTAT_OUTPUT_EN (1 << 4)
> +#define I2CSTAT_START_BUSY (1 << 5)
> +
> +
> +#if EXYNOS4_I2C_DEBUG
> +#define DPRINT(fmt, args...) \
> + do {fprintf(stderr, "QEMU I2C: "fmt, ## args); } while (0)
> +
> +static const char *exynos4_i2c_regs_names[] = {
> + [I2CCON_ADDR >> 2] = "I2CCON",
> + [I2CSTAT_ADDR >> 2] = "I2CSTAT",
> + [I2CADD_ADDR >> 2] = "I2CADD",
> + [I2CDS_ADDR >> 2] = "I2CDS",
> + [I2CLC_ADDR >> 2] = "I2CLC",
> +};
> +
> +#else
> +#define DPRINT(fmt, args...) do { } while (0)
> +#endif
> +
> +typedef struct Exynos4210I2CSlave Exynos4210I2CSlave;
> +
> +typedef struct Exynos4210I2CState {
> + SysBusDevice busdev;
> + MemoryRegion iomem;
> + i2c_bus *bus;
> + Exynos4210I2CSlave *slave;
> + qemu_irq irq;
> + QEMUTimer *receive_timer;
> + QEMUTimer *send_timer;
> +
> + uint8_t i2ccon;
> + uint8_t i2cstat;
> + uint8_t i2cadd;
> + uint8_t i2cds;
> + uint8_t i2clc;
> +} Exynos4210I2CState;
> +
> +enum {
> + idle,
> + start_bit_received,
> + sending_data,
> + receiving_data
> +};
> +
> +struct Exynos4210I2CSlave {
> + I2CSlave i2c;
> + Exynos4210I2CState *host;
> + uint32_t status;
> +};
> +
> +
> +static inline void exynos4210_i2c_set_irq(Exynos4210I2CState *s)
> +{
> + if (s->i2ccon & I2CCON_INTERRUPTS_EN) {
> + s->i2ccon |= I2CCON_INTERRUPT_PEND;
> + qemu_irq_raise(s->irq);
> + }
> +}
> +
> +static void exynos4210_i2c_data_received(void *opaque)
> +{
> + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
> + int ret;
> +
> + ret = i2c_recv(s->bus);
> + if (ret < 0) {
> + s->i2cstat |= I2CSTAT_LAST_BIT; /* Data is not acknowledged */
> + } else {
> + s->i2cds = ret;
> + s->i2cstat &= ~I2CSTAT_LAST_BIT; /* Data is acknowledged */
> + }
> + exynos4210_i2c_set_irq(s);
> +}
> +
> +static void exynos4210_i2c_data_sent(void *opaque)
> +{
> + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
> +
> + if (i2c_send(s->bus, s->i2cds) < 0) {
> + s->i2cstat |= I2CSTAT_LAST_BIT; /* Data is not acknowledged */
> + } else {
> + s->i2cstat &= ~I2CSTAT_LAST_BIT; /* Data is acknowledged */
> + }
> + exynos4210_i2c_set_irq(s);
> +}
> +
> +static uint64_t exynos4210_i2c_read(void *opaque, target_phys_addr_t offset,
> + unsigned size)
> +{
> + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
> + uint8_t value;
> +
> + switch (offset) {
> + case I2CCON_ADDR:
> + value = s->i2ccon;
> + break;
> + case I2CSTAT_ADDR:
> + value = s->i2cstat;
> + break;
> + case I2CADD_ADDR:
> + value = s->i2cadd;
> + break;
> + case I2CDS_ADDR:
> + value = s->i2cds;
> + break;
> + case I2CLC_ADDR:
> + value = s->i2clc;
> + break;
> + default:
> + value = 0;
> + DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset);
> + break;
> + }
> +
> + DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_regs_names[offset >> 2],
> + (unsigned int)offset, value);
Just for security it's better to check offset to not go out of the array
borders.
> + return value;
> +}
> +
> +static void exynos4210_i2c_write(void *opaque, target_phys_addr_t offset,
> + uint64_t value, unsigned size)
> +{
> + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
> + uint8_t v = value & 0xff;
> +
> + DPRINT("write %s [0x%02x] <- 0x%02x\n", exynos4_i2c_regs_names[offset >> 2],
> + (unsigned int)offset, v);
> +
> + switch (offset) {
> + case I2CCON_ADDR:
> + if ((s->i2ccon & I2CCON_INTERRUPT_PEND) &&
> + !(v & I2CCON_INTERRUPT_PEND)) {
> + s->i2ccon &= ~I2CCON_INTERRUPT_PEND;
> + if (!(s->i2ccon & I2CCON_INTERRUPTS_EN)) {
> + s->i2cstat &= ~I2CSTAT_START_BUSY;
> + }
> + qemu_irq_lower(s->irq);
> +
> + if (!(s->i2cstat & I2CSTAT_OUTPUT_EN) ||
> + !(s->i2cstat & I2CSTAT_START_BUSY)) {
> + s->i2cstat &= ~I2CSTAT_START_BUSY; /* Line is not busy */
> + break;
> + }
> + /* Continue operation if transfer is ongoing */
> + if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) {
> + qemu_mod_timer(s->receive_timer, qemu_get_clock_ns(vm_clock)+1);
> + } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) {
> + qemu_mod_timer(s->send_timer, qemu_get_clock_ns(vm_clock) + 1);
Why do you need to use timers here? Why not just call the corresponding
functions directly? I can guess because when sending new message Linux
driver first enables the interrupt and then writes the stat register.
Am I right?
> + }
> + }
> +
> + /* bit 4 can't be written */
> + s->i2ccon = (v & ~I2CCON_INTERRUPT_PEND) |
> + (s->i2ccon & I2CCON_INTERRUPT_PEND);
> + break;
> + case I2CSTAT_ADDR:
> + s->i2cstat =
> + (s->i2cstat & I2CSTAT_START_BUSY) | (v & ~I2CSTAT_START_BUSY);
> +
> + if (!(s->i2cstat & I2CSTAT_OUTPUT_EN)) {
> + s->i2cstat &= ~I2CSTAT_START_BUSY;
> + qemu_del_timer(s->receive_timer);
> + qemu_del_timer(s->send_timer);
> + break;
> + }
> +
> + /* Nothing to do if in i2c slave mode */
> + if (!I2C_IN_MASTER_MODE(s->i2cstat)) {
> + break;
> + }
> +
> + if (v & I2CSTAT_START_BUSY) {
> + s->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */
> + /* Generate start bit and send slave address */
> + i2c_start_transfer(s->bus, s->i2cds >> 1, s->i2cds & 0x1);
> + exynos4210_i2c_data_sent(s);
> + } else {
> + i2c_end_transfer(s->bus);
> + /* Linux driver may start next i2c transaction before it
> + * acknowledges an interrupt request from previous transaction.
> + * This may result in timeout waiting for I2C bus idle and
> + * corrupted transfer. Releasing I2C bus only after interrupt is
> + * accepted prevents this corruption. */
> + if (!(s->i2ccon & I2CCON_INTERRUPT_PEND)) {
> + s->i2cstat &= ~I2CSTAT_START_BUSY;
> + }
> + }
> + break;
> + case I2CADD_ADDR:
> + if ((s->i2cstat & I2CSTAT_OUTPUT_EN) == 0) {
> + s->i2cadd = v;
> + i2c_set_slave_address(&s->slave->i2c, v);
> + }
> + break;
> + case I2CDS_ADDR:
> + if (s->i2cstat & I2CSTAT_OUTPUT_EN) {
> + s->i2cds = v;
> + }
> + break;
> + case I2CLC_ADDR:
> + s->i2clc = v;
> + break;
> + default:
> + DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset);
> + break;
> + }
> +}
> +
> +static const MemoryRegionOps exynos4210_i2c_ops = {
> + .read = exynos4210_i2c_read,
> + .write = exynos4210_i2c_write,
> + .endianness = DEVICE_NATIVE_ENDIAN,
> +};
> +
> +static const VMStateDescription exynos4210_i2c_slave_vmstate = {
> + .name = TYPE_EXYNOS4_I2C_SLAVE,
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_I2C_SLAVE(i2c, Exynos4210I2CSlave),
> + VMSTATE_UINT32(status, Exynos4210I2CSlave),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static const VMStateDescription exynos4210_i2c_vmstate = {
> + .name = TYPE_EXYNOS4_I2C,
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_UINT8(i2ccon, Exynos4210I2CState),
> + VMSTATE_UINT8(i2cstat, Exynos4210I2CState),
> + VMSTATE_UINT8(i2cds, Exynos4210I2CState),
> + VMSTATE_UINT8(i2clc, Exynos4210I2CState),
Should i2cadd also be saved here?
> + VMSTATE_TIMER(receive_timer, Exynos4210I2CState),
> + VMSTATE_TIMER(send_timer, Exynos4210I2CState),
> + VMSTATE_STRUCT_POINTER(slave, Exynos4210I2CState,
> + exynos4210_i2c_slave_vmstate, Exynos4210I2CSlave *),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static void exynos4210_i2c_reset(DeviceState *d)
> +{
> + Exynos4210I2CState *s = EXYNOS4_I2C(d);
> +
> + s->i2ccon = 0x0f; /* 0x0X */
> + s->i2cstat = 0x00;
> + s->i2cds = 0xff; /* 0xXX */
> + s->i2clc = 0x00;
> + s->i2cadd = 0xff; /* 0xXX */
> + s->slave->status = idle;
> + qemu_del_timer(s->receive_timer);
> + qemu_del_timer(s->send_timer);
> +}
> +
> +static int exynos4210_i2c_init(SysBusDevice *dev)
> +{
> + Exynos4210I2CState *s = EXYNOS4_I2C(dev);
> + DeviceState *slave;
> +
> + memory_region_init_io(&s->iomem, &exynos4210_i2c_ops, s, TYPE_EXYNOS4_I2C,
> + EXYNOS4_I2C_MEM_SIZE);
> + sysbus_init_mmio(dev, &s->iomem);
> + sysbus_init_irq(dev, &s->irq);
> + s->bus = i2c_init_bus(&dev->qdev, "i2c");
> + s->receive_timer =
> + qemu_new_timer_ns(vm_clock, exynos4210_i2c_data_received, s);
> + s->send_timer = qemu_new_timer_ns(vm_clock, exynos4210_i2c_data_sent, s);
> +
> + /* i2c slave initialization */
> + slave = i2c_create_slave(s->bus, TYPE_EXYNOS4_I2C_SLAVE, 0xff);
> + s->slave = EXYNOS4_I2C_SLAVE(slave);
> + s->slave->host = s;
> +
> + return 0;
> +}
> +
> +static void exynos4210_i2c_class_init(ObjectClass *class, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(class);
> + SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(class);
> +
> + dc->vmsd = &exynos4210_i2c_vmstate;
> + dc->reset = exynos4210_i2c_reset;
> + sbc->init = exynos4210_i2c_init;
> +}
> +
> +static TypeInfo exynos4210_i2c_info = {
> + .name = TYPE_EXYNOS4_I2C,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(Exynos4210I2CState),
> + .class_init = exynos4210_i2c_class_init,
> +};
> +
> +static int exynos4210_i2c_slave_init(I2CSlave *i2c)
> +{
> + Exynos4210I2CSlave *s = EXYNOS4_I2C_SLAVE(i2c);
> +
> + s->status = idle;
> + return 0;
> +}
> +
> +static void exynos4210_i2c_slave_event(I2CSlave *i2c, enum i2c_event event)
> +{
> + Exynos4210I2CSlave *s = EXYNOS4_I2C_SLAVE(i2c);
> + Exynos4210I2CState *host = s->host;
> +
> + if (!(host->i2cstat & I2CSTAT_OUTPUT_EN) ||
> + I2C_IN_MASTER_MODE(host->i2cstat)) {
> + return;
> + }
> +
> + switch (event) {
> + case I2C_START_RECV:
> + case I2C_START_SEND:
> + host->i2cstat &= ~(I2CSTAT_SLAVE_ADDR_ZERO | I2CSTAT_SLAVE_ADDR_MATCH);
> + s->status = start_bit_received;
> + host->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */
> + break;
> + case I2C_FINISH:
> + s->status = idle;
> + host->i2cstat &= ~(I2CSTAT_SLAVE_ADDR_ZERO | I2CSTAT_SLAVE_ADDR_MATCH |
> + I2CSTAT_START_BUSY);
> + break;
> + default:
> + break;
> + }
> +}
> +
> +static int exynos4210_i2c_slave_recv(I2CSlave *i2c)
> +{
> + Exynos4210I2CSlave *s = EXYNOS4_I2C_SLAVE(i2c);
> + Exynos4210I2CState *host = s->host;
> +
> + if (!(host->i2cstat & I2CSTAT_OUTPUT_EN) ||
> + I2C_IN_MASTER_MODE(host->i2cstat)) {
> + return -1;
> + }
> +
> + if ((s->status != sending_data) || (EXYNOS4_I2C_MODE(host->i2cstat) !=
> + I2CSTAT_MODE_SLAVE_Tx)) {
> + return -1;
> + }
> +
> + exynos4210_i2c_set_irq(host);
> + return host->i2cds;
> +}
> +
> +static int exynos4210_i2c_slave_send(I2CSlave *i2c, uint8_t data)
> +{
> + Exynos4210I2CSlave *s = EXYNOS4_I2C_SLAVE(i2c);
> + Exynos4210I2CState *host = s->host;
> +
> + if (!(host->i2cstat & I2CSTAT_OUTPUT_EN) ||
> + I2C_IN_MASTER_MODE(host->i2cstat)) {
> + return -1;
> + }
> +
> + if (s->status == start_bit_received) {
> + host->i2cds = data;
> + s->status = idle;
> + if ((data & 0xFE) == 0) {
> + host->i2cstat |= I2CSTAT_SLAVE_ADDR_ZERO;
> + return -1;
> + }
> + if ((host->i2cadd & 0xFE) != (data & 0xFE)) {
> + return -1;
> + }
> + host->i2cstat |= I2CSTAT_SLAVE_ADDR_MATCH;
> + (data & 0x1) ? (s->status = receiving_data) :
> + (s->status = sending_data);
> + exynos4210_i2c_set_irq(host);
> + return 0;
> + }
> +
> + if ((s->status != receiving_data) || (EXYNOS4_I2C_MODE(host->i2cstat) !=
> + I2CSTAT_MODE_SLAVE_Rx)) {
> + return -1;
> + }
> +
> + host->i2cds = data;
> + exynos4210_i2c_set_irq(host);
> + return 0;
> +}
> +
> +static void exynos4210_i2cslave_class_init(ObjectClass *klass, void *data)
> +{
> + I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
> +
> + k->init = exynos4210_i2c_slave_init;
> + k->event = exynos4210_i2c_slave_event;
> + k->recv = exynos4210_i2c_slave_recv;
> + k->send = exynos4210_i2c_slave_send;
> +}
> +
> +static TypeInfo exynos4210_i2c_slave = {
> + .name = TYPE_EXYNOS4_I2C_SLAVE,
> + .parent = TYPE_I2C_SLAVE,
> + .instance_size = sizeof(Exynos4210I2CSlave),
> + .class_init = exynos4210_i2cslave_class_init,
> +};
> +
> +static void exynos4210_i2c_register_types(void)
> +{
> + type_register_static(&exynos4210_i2c_slave);
> + type_register_static(&exynos4210_i2c_info);
> +}
> +
> +type_init(exynos4210_i2c_register_types)
PS: This I2C implementation seems to be rather complex. Did you check
what we wrote for S5PC110 (ah, I also learnt that Samsung renamed it to
Exynos3210) almost three years ago
(https://gitorious.org/samsung-linux-platform/qemu/blobs/s5pc110-stable/hw/s5pc1xx_i2c.c)?
When I compared documentation and kernel driver sources for both devices
I couldn't find any significant difference. Generally speaking, is it
worth redoing everything from scratch instead of updating?
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation
2012-04-03 13:54 ` Dmitry Zhurikhin
@ 2012-04-03 14:54 ` Igor Mitsyanko
0 siblings, 0 replies; 18+ messages in thread
From: Igor Mitsyanko @ 2012-04-03 14:54 UTC (permalink / raw)
To: Dmitry Zhurikhin
Cc: peter.maydell, e.voevodin, qemu-devel, kyungmin.park, d.solodkiy,
m.kozlov, afaerber
On 04/03/2012 05:54 PM, Dmitry Zhurikhin wrote:
> Let me add my two cents here. I tested this patch slightly and didn't
> find any problems.
>
Great, thanks!
> On 2012-03-15 11:35, Igor Mitsyanko wrote:
>> Create 9 exynos4210 i2c interfaces.
>>
>> Signed-off-by: Igor Mitsyanko<i.mitsyanko@samsung.com>
>> ---
>> Makefile.target | 1 +
>> hw/exynos4210.c | 26 +++
>> hw/exynos4210.h | 3 +
>> hw/exynos4210_i2c.c | 469 +++++++++++++++++++++++++++++++++++++++++++++++++++
>> 4 files changed, 499 insertions(+), 0 deletions(-)
>> create mode 100644 hw/exynos4210_i2c.c
>>
>> diff --git a/Makefile.target b/Makefile.target
>> index eb25941..5e8a1d4 100644
>> --- a/Makefile.target
>> +++ b/Makefile.target
>> @@ -357,6 +357,7 @@ obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
>> obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
>> obj-arm-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
>> obj-arm-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o
>> +obj-arm-y += exynos4210_i2c.o
>> obj-arm-y += arm_l2x0.o
>> obj-arm-y += arm_mptimer.o a15mpcore.o
>> obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
>> diff --git a/hw/exynos4210.c b/hw/exynos4210.c
>> index f904370..464f157 100644
>> --- a/hw/exynos4210.c
>> +++ b/hw/exynos4210.c
>> @@ -35,6 +35,13 @@
>> /* MCT */
>> #define EXYNOS4210_MCT_BASE_ADDR 0x10050000
>>
>> +/* I2C */
>> +#define EXYNOS4210_I2C_SHIFT 0x00010000
>> +#define EXYNOS4210_I2C_BASE_ADDR 0x13860000
>> +/* Interrupt Group of External Interrupt Combiner for I2C */
>> +#define EXYNOS4210_I2C_INTG 27
>> +#define EXYNOS4210_HDMI_INTG 16
>> +
>> /* UART's definitions */
>> #define EXYNOS4210_UART0_BASE_ADDR 0x13800000
>> #define EXYNOS4210_UART1_BASE_ADDR 0x13810000
>> @@ -242,6 +249,25 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
>> s->irq_table[exynos4210_get_irq(35, 3)]);
>> sysbus_mmio_map(busdev, 0, EXYNOS4210_MCT_BASE_ADDR);
>>
>> + /*** I2C ***/
>> + for (n = 0; n< EXYNOS4210_I2C_NUMBER; n++) {
>> + uint32_t addr = EXYNOS4210_I2C_BASE_ADDR + EXYNOS4210_I2C_SHIFT * n;
>> + qemu_irq i2c_irq;
>> +
>> + if (n< 8) {
>> + i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_I2C_INTG, n)];
>> + } else {
>> + i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_HDMI_INTG, 1)];
>> + }
>> +
>> + dev = qdev_create(NULL, "exynos4210.i2c");
>> + qdev_init_nofail(dev);
>> + busdev = sysbus_from_qdev(dev);
>> + sysbus_connect_irq(busdev, 0, i2c_irq);
>> + sysbus_mmio_map(busdev, 0, addr);
>> + s->i2c_if[n] = (i2c_bus *)qdev_get_child_bus(dev, "i2c");
>> + }
>> +
>> /*** UARTs ***/
>> exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR,
>> EXYNOS4210_UART0_FIFO_SIZE, 0, NULL,
>> diff --git a/hw/exynos4210.h b/hw/exynos4210.h
>> index c112e03..1205fb5 100644
>> --- a/hw/exynos4210.h
>> +++ b/hw/exynos4210.h
>> @@ -74,6 +74,8 @@
>> #define EXYNOS4210_EXT_GIC_NIRQ (160-32)
>> #define EXYNOS4210_INT_GIC_NIRQ 64
>>
>> +#define EXYNOS4210_I2C_NUMBER 9
>> +
>> typedef struct Exynos4210Irq {
>> qemu_irq int_combiner_irq[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ];
>> qemu_irq ext_combiner_irq[EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ];
>> @@ -95,6 +97,7 @@ typedef struct Exynos4210State {
>> MemoryRegion dram1_mem;
>> MemoryRegion boot_secondary;
>> MemoryRegion bootreg_mem;
>> + i2c_bus *i2c_if[EXYNOS4210_I2C_NUMBER];
>> } Exynos4210State;
>>
>> Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
>> diff --git a/hw/exynos4210_i2c.c b/hw/exynos4210_i2c.c
>> new file mode 100644
>> index 0000000..42f770d
>> --- /dev/null
>> +++ b/hw/exynos4210_i2c.c
>> @@ -0,0 +1,469 @@
>> +/*
>> + * Exynos4210 I2C Bus Serial Interface Emulation
>> + *
>> + * Copyright (C) 2012 Samsung Electronics Co Ltd.
>> + * Maksim Kozlov,<m.kozlov@samsung.com>
>> + * Igor Mitsyanko,<i.mitsyanko@samsung.com>
>> + *
>> + * 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 of the License, or
>> + * (at your option) any later version.
>> + *
>> + * 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-timer.h"
>> +#include "sysbus.h"
>> +#include "i2c.h"
>> +
>> +#ifndef EXYNOS4_I2C_DEBUG
>> +#define EXYNOS4_I2C_DEBUG 0
>> +#endif
>> +
>> +#define TYPE_EXYNOS4_I2C "exynos4210.i2c"
>> +#define TYPE_EXYNOS4_I2C_SLAVE "exynos4210.i2c-slave"
>> +#define EXYNOS4_I2C(obj) \
>> + OBJECT_CHECK(Exynos4210I2CState, (obj), TYPE_EXYNOS4_I2C)
>> +#define EXYNOS4_I2C_SLAVE(obj) \
>> + OBJECT_CHECK(Exynos4210I2CSlave, (obj), TYPE_EXYNOS4_I2C_SLAVE)
>> +
>> +/* Exynos4210 I2C memory map */
>> +#define EXYNOS4_I2C_MEM_SIZE 0x14
>> +#define I2CCON_ADDR 0x00 /* control register */
>> +#define I2CSTAT_ADDR 0x04 /* control/status register */
>> +#define I2CADD_ADDR 0x08 /* address register */
>> +#define I2CDS_ADDR 0x0c /* data shift register */
>> +#define I2CLC_ADDR 0x10 /* line control register */
>> +
>> +#define I2CCON_INTERRUPTS_EN (1<< 5)
>> +#define I2CCON_INTERRUPT_PEND (1<< 4)
>> +
>> +#define EXYNOS4_I2C_MODE(reg) (((reg)>> 6)& 3)
>> +#define I2C_IN_MASTER_MODE(reg) (((reg)>> 6)& 2)
>> +#define I2CSTAT_MODE_SLAVE_Rx 0x0
>> +#define I2CSTAT_MODE_SLAVE_Tx 0x1
>> +#define I2CMODE_MASTER_Rx 0x2
>> +#define I2CMODE_MASTER_Tx 0x3
>> +#define I2CSTAT_LAST_BIT (1<< 0)
>> +#define I2CSTAT_SLAVE_ADDR_ZERO (1<< 1)
>> +#define I2CSTAT_SLAVE_ADDR_MATCH (1<< 2)
>> +#define I2CSTAT_ARBITR_STATUS (1<< 3)
>> +#define I2CSTAT_OUTPUT_EN (1<< 4)
>> +#define I2CSTAT_START_BUSY (1<< 5)
>> +
>> +
>> +#if EXYNOS4_I2C_DEBUG
>> +#define DPRINT(fmt, args...) \
>> + do {fprintf(stderr, "QEMU I2C: "fmt, ## args); } while (0)
>> +
>> +static const char *exynos4_i2c_regs_names[] = {
>> + [I2CCON_ADDR>> 2] = "I2CCON",
>> + [I2CSTAT_ADDR>> 2] = "I2CSTAT",
>> + [I2CADD_ADDR>> 2] = "I2CADD",
>> + [I2CDS_ADDR>> 2] = "I2CDS",
>> + [I2CLC_ADDR>> 2] = "I2CLC",
>> +};
>> +
>> +#else
>> +#define DPRINT(fmt, args...) do { } while (0)
>> +#endif
>> +
>> +typedef struct Exynos4210I2CSlave Exynos4210I2CSlave;
>> +
>> +typedef struct Exynos4210I2CState {
>> + SysBusDevice busdev;
>> + MemoryRegion iomem;
>> + i2c_bus *bus;
>> + Exynos4210I2CSlave *slave;
>> + qemu_irq irq;
>> + QEMUTimer *receive_timer;
>> + QEMUTimer *send_timer;
>> +
>> + uint8_t i2ccon;
>> + uint8_t i2cstat;
>> + uint8_t i2cadd;
>> + uint8_t i2cds;
>> + uint8_t i2clc;
>> +} Exynos4210I2CState;
>> +
>> +enum {
>> + idle,
>> + start_bit_received,
>> + sending_data,
>> + receiving_data
>> +};
>> +
>> +struct Exynos4210I2CSlave {
>> + I2CSlave i2c;
>> + Exynos4210I2CState *host;
>> + uint32_t status;
>> +};
>> +
>> +
>> +static inline void exynos4210_i2c_set_irq(Exynos4210I2CState *s)
>> +{
>> + if (s->i2ccon& I2CCON_INTERRUPTS_EN) {
>> + s->i2ccon |= I2CCON_INTERRUPT_PEND;
>> + qemu_irq_raise(s->irq);
>> + }
>> +}
>> +
>> +static void exynos4210_i2c_data_received(void *opaque)
>> +{
>> + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
>> + int ret;
>> +
>> + ret = i2c_recv(s->bus);
>> + if (ret< 0) {
>> + s->i2cstat |= I2CSTAT_LAST_BIT; /* Data is not acknowledged */
>> + } else {
>> + s->i2cds = ret;
>> + s->i2cstat&= ~I2CSTAT_LAST_BIT; /* Data is acknowledged */
>> + }
>> + exynos4210_i2c_set_irq(s);
>> +}
>> +
>> +static void exynos4210_i2c_data_sent(void *opaque)
>> +{
>> + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
>> +
>> + if (i2c_send(s->bus, s->i2cds)< 0) {
>> + s->i2cstat |= I2CSTAT_LAST_BIT; /* Data is not acknowledged */
>> + } else {
>> + s->i2cstat&= ~I2CSTAT_LAST_BIT; /* Data is acknowledged */
>> + }
>> + exynos4210_i2c_set_irq(s);
>> +}
>> +
>> +static uint64_t exynos4210_i2c_read(void *opaque, target_phys_addr_t offset,
>> + unsigned size)
>> +{
>> + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
>> + uint8_t value;
>> +
>> + switch (offset) {
>> + case I2CCON_ADDR:
>> + value = s->i2ccon;
>> + break;
>> + case I2CSTAT_ADDR:
>> + value = s->i2cstat;
>> + break;
>> + case I2CADD_ADDR:
>> + value = s->i2cadd;
>> + break;
>> + case I2CDS_ADDR:
>> + value = s->i2cds;
>> + break;
>> + case I2CLC_ADDR:
>> + value = s->i2clc;
>> + break;
>> + default:
>> + value = 0;
>> + DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset);
>> + break;
>> + }
>> +
>> + DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_regs_names[offset>> 2],
>> + (unsigned int)offset, value);
> Just for security it's better to check offset to not go out of the array
> borders.
Ok, good point.
>> + return value;
>> +}
>> +
>> +static void exynos4210_i2c_write(void *opaque, target_phys_addr_t offset,
>> + uint64_t value, unsigned size)
>> +{
>> + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque;
>> + uint8_t v = value& 0xff;
>> +
>> + DPRINT("write %s [0x%02x]<- 0x%02x\n", exynos4_i2c_regs_names[offset>> 2],
>> + (unsigned int)offset, v);
>> +
>> + switch (offset) {
>> + case I2CCON_ADDR:
>> + if ((s->i2ccon& I2CCON_INTERRUPT_PEND)&&
>> + !(v& I2CCON_INTERRUPT_PEND)) {
>> + s->i2ccon&= ~I2CCON_INTERRUPT_PEND;
>> + if (!(s->i2ccon& I2CCON_INTERRUPTS_EN)) {
>> + s->i2cstat&= ~I2CSTAT_START_BUSY;
>> + }
>> + qemu_irq_lower(s->irq);
>> +
>> + if (!(s->i2cstat& I2CSTAT_OUTPUT_EN) ||
>> + !(s->i2cstat& I2CSTAT_START_BUSY)) {
>> + s->i2cstat&= ~I2CSTAT_START_BUSY; /* Line is not busy */
>> + break;
>> + }
>> + /* Continue operation if transfer is ongoing */
>> + if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) {
>> + qemu_mod_timer(s->receive_timer, qemu_get_clock_ns(vm_clock)+1);
>> + } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) {
>> + qemu_mod_timer(s->send_timer, qemu_get_clock_ns(vm_clock) + 1);
> Why do you need to use timers here? Why not just call the corresponding
> functions directly? I can guess because when sending new message Linux
> driver first enables the interrupt and then writes the stat register.
> Am I right?
Yes, and I wanted to emulate a small delay between qemu_irq_lower()
and qemu_raise_irq(), But I've just tried to replace timer usage with direct
functions calling and it still works stably, I think I wil remove timers
in next version.
>> + }
>> + }
>> +
>> + /* bit 4 can't be written */
>> + s->i2ccon = (v& ~I2CCON_INTERRUPT_PEND) |
>> + (s->i2ccon& I2CCON_INTERRUPT_PEND);
>> + break;
>> + case I2CSTAT_ADDR:
>> + s->i2cstat =
>> + (s->i2cstat& I2CSTAT_START_BUSY) | (v& ~I2CSTAT_START_BUSY);
>> +
>> + if (!(s->i2cstat& I2CSTAT_OUTPUT_EN)) {
>> + s->i2cstat&= ~I2CSTAT_START_BUSY;
>> + qemu_del_timer(s->receive_timer);
>> + qemu_del_timer(s->send_timer);
>> + break;
>> + }
>> +
>> + /* Nothing to do if in i2c slave mode */
>> + if (!I2C_IN_MASTER_MODE(s->i2cstat)) {
>> + break;
>> + }
>> +
>> + if (v& I2CSTAT_START_BUSY) {
>> + s->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */
>> + /* Generate start bit and send slave address */
>> + i2c_start_transfer(s->bus, s->i2cds>> 1, s->i2cds& 0x1);
>> + exynos4210_i2c_data_sent(s);
>> + } else {
>> + i2c_end_transfer(s->bus);
>> + /* Linux driver may start next i2c transaction before it
>> + * acknowledges an interrupt request from previous transaction.
>> + * This may result in timeout waiting for I2C bus idle and
>> + * corrupted transfer. Releasing I2C bus only after interrupt is
>> + * accepted prevents this corruption. */
>> + if (!(s->i2ccon& I2CCON_INTERRUPT_PEND)) {
>> + s->i2cstat&= ~I2CSTAT_START_BUSY;
>> + }
>> + }
>> + break;
>> + case I2CADD_ADDR:
>> + if ((s->i2cstat& I2CSTAT_OUTPUT_EN) == 0) {
>> + s->i2cadd = v;
>> + i2c_set_slave_address(&s->slave->i2c, v);
>> + }
>> + break;
>> + case I2CDS_ADDR:
>> + if (s->i2cstat& I2CSTAT_OUTPUT_EN) {
>> + s->i2cds = v;
>> + }
>> + break;
>> + case I2CLC_ADDR:
>> + s->i2clc = v;
>> + break;
>> + default:
>> + DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset);
>> + break;
>> + }
>> +}
>> +
>> +static const MemoryRegionOps exynos4210_i2c_ops = {
>> + .read = exynos4210_i2c_read,
>> + .write = exynos4210_i2c_write,
>> + .endianness = DEVICE_NATIVE_ENDIAN,
>> +};
>> +
>> +static const VMStateDescription exynos4210_i2c_slave_vmstate = {
>> + .name = TYPE_EXYNOS4_I2C_SLAVE,
>> + .version_id = 1,
>> + .minimum_version_id = 1,
>> + .fields = (VMStateField[]) {
>> + VMSTATE_I2C_SLAVE(i2c, Exynos4210I2CSlave),
>> + VMSTATE_UINT32(status, Exynos4210I2CSlave),
>> + VMSTATE_END_OF_LIST()
>> + }
>> +};
>> +
>> +static const VMStateDescription exynos4210_i2c_vmstate = {
>> + .name = TYPE_EXYNOS4_I2C,
>> + .version_id = 1,
>> + .minimum_version_id = 1,
>> + .fields = (VMStateField[]) {
>> + VMSTATE_UINT8(i2ccon, Exynos4210I2CState),
>> + VMSTATE_UINT8(i2cstat, Exynos4210I2CState),
>> + VMSTATE_UINT8(i2cds, Exynos4210I2CState),
>> + VMSTATE_UINT8(i2clc, Exynos4210I2CState),
> Should i2cadd also be saved here?
Yes
>> + VMSTATE_TIMER(receive_timer, Exynos4210I2CState),
>> + VMSTATE_TIMER(send_timer, Exynos4210I2CState),
>> + VMSTATE_STRUCT_POINTER(slave, Exynos4210I2CState,
>> + exynos4210_i2c_slave_vmstate, Exynos4210I2CSlave *),
>> + VMSTATE_END_OF_LIST()
>> + }
>> +};
>> +
>> +static void exynos4210_i2c_reset(DeviceState *d)
>> +{
>> + Exynos4210I2CState *s = EXYNOS4_I2C(d);
>> +
>> + s->i2ccon = 0x0f; /* 0x0X */
>> + s->i2cstat = 0x00;
>> + s->i2cds = 0xff; /* 0xXX */
>> + s->i2clc = 0x00;
>> + s->i2cadd = 0xff; /* 0xXX */
>> + s->slave->status = idle;
>> + qemu_del_timer(s->receive_timer);
>> + qemu_del_timer(s->send_timer);
>> +}
>> +
>> +static int exynos4210_i2c_init(SysBusDevice *dev)
>> +{
>> + Exynos4210I2CState *s = EXYNOS4_I2C(dev);
>> + DeviceState *slave;
>> +
>> + memory_region_init_io(&s->iomem,&exynos4210_i2c_ops, s, TYPE_EXYNOS4_I2C,
>> + EXYNOS4_I2C_MEM_SIZE);
>> + sysbus_init_mmio(dev,&s->iomem);
>> + sysbus_init_irq(dev,&s->irq);
>> + s->bus = i2c_init_bus(&dev->qdev, "i2c");
>> + s->receive_timer =
>> + qemu_new_timer_ns(vm_clock, exynos4210_i2c_data_received, s);
>> + s->send_timer = qemu_new_timer_ns(vm_clock, exynos4210_i2c_data_sent, s);
>> +
>> + /* i2c slave initialization */
>> + slave = i2c_create_slave(s->bus, TYPE_EXYNOS4_I2C_SLAVE, 0xff);
>> + s->slave = EXYNOS4_I2C_SLAVE(slave);
>> + s->slave->host = s;
>> +
>> + return 0;
>> +}
>> +
>> +static void exynos4210_i2c_class_init(ObjectClass *class, void *data)
>> +{
>> + DeviceClass *dc = DEVICE_CLASS(class);
>> + SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(class);
>> +
>> + dc->vmsd =&exynos4210_i2c_vmstate;
>> + dc->reset = exynos4210_i2c_reset;
>> + sbc->init = exynos4210_i2c_init;
>> +}
>> +
>> +static TypeInfo exynos4210_i2c_info = {
>> + .name = TYPE_EXYNOS4_I2C,
>> + .parent = TYPE_SYS_BUS_DEVICE,
>> + .instance_size = sizeof(Exynos4210I2CState),
>> + .class_init = exynos4210_i2c_class_init,
>> +};
>> +
>> +static int exynos4210_i2c_slave_init(I2CSlave *i2c)
>> +{
>> + Exynos4210I2CSlave *s = EXYNOS4_I2C_SLAVE(i2c);
>> +
>> + s->status = idle;
>> + return 0;
>> +}
>> +
>> +static void exynos4210_i2c_slave_event(I2CSlave *i2c, enum i2c_event event)
>> +{
>> + Exynos4210I2CSlave *s = EXYNOS4_I2C_SLAVE(i2c);
>> + Exynos4210I2CState *host = s->host;
>> +
>> + if (!(host->i2cstat& I2CSTAT_OUTPUT_EN) ||
>> + I2C_IN_MASTER_MODE(host->i2cstat)) {
>> + return;
>> + }
>> +
>> + switch (event) {
>> + case I2C_START_RECV:
>> + case I2C_START_SEND:
>> + host->i2cstat&= ~(I2CSTAT_SLAVE_ADDR_ZERO | I2CSTAT_SLAVE_ADDR_MATCH);
>> + s->status = start_bit_received;
>> + host->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */
>> + break;
>> + case I2C_FINISH:
>> + s->status = idle;
>> + host->i2cstat&= ~(I2CSTAT_SLAVE_ADDR_ZERO | I2CSTAT_SLAVE_ADDR_MATCH |
>> + I2CSTAT_START_BUSY);
>> + break;
>> + default:
>> + break;
>> + }
>> +}
>> +
>> +static int exynos4210_i2c_slave_recv(I2CSlave *i2c)
>> +{
>> + Exynos4210I2CSlave *s = EXYNOS4_I2C_SLAVE(i2c);
>> + Exynos4210I2CState *host = s->host;
>> +
>> + if (!(host->i2cstat& I2CSTAT_OUTPUT_EN) ||
>> + I2C_IN_MASTER_MODE(host->i2cstat)) {
>> + return -1;
>> + }
>> +
>> + if ((s->status != sending_data) || (EXYNOS4_I2C_MODE(host->i2cstat) !=
>> + I2CSTAT_MODE_SLAVE_Tx)) {
>> + return -1;
>> + }
>> +
>> + exynos4210_i2c_set_irq(host);
>> + return host->i2cds;
>> +}
>> +
>> +static int exynos4210_i2c_slave_send(I2CSlave *i2c, uint8_t data)
>> +{
>> + Exynos4210I2CSlave *s = EXYNOS4_I2C_SLAVE(i2c);
>> + Exynos4210I2CState *host = s->host;
>> +
>> + if (!(host->i2cstat& I2CSTAT_OUTPUT_EN) ||
>> + I2C_IN_MASTER_MODE(host->i2cstat)) {
>> + return -1;
>> + }
>> +
>> + if (s->status == start_bit_received) {
>> + host->i2cds = data;
>> + s->status = idle;
>> + if ((data& 0xFE) == 0) {
>> + host->i2cstat |= I2CSTAT_SLAVE_ADDR_ZERO;
>> + return -1;
>> + }
>> + if ((host->i2cadd& 0xFE) != (data& 0xFE)) {
>> + return -1;
>> + }
>> + host->i2cstat |= I2CSTAT_SLAVE_ADDR_MATCH;
>> + (data& 0x1) ? (s->status = receiving_data) :
>> + (s->status = sending_data);
>> + exynos4210_i2c_set_irq(host);
>> + return 0;
>> + }
>> +
>> + if ((s->status != receiving_data) || (EXYNOS4_I2C_MODE(host->i2cstat) !=
>> + I2CSTAT_MODE_SLAVE_Rx)) {
>> + return -1;
>> + }
>> +
>> + host->i2cds = data;
>> + exynos4210_i2c_set_irq(host);
>> + return 0;
>> +}
>> +
>> +static void exynos4210_i2cslave_class_init(ObjectClass *klass, void *data)
>> +{
>> + I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
>> +
>> + k->init = exynos4210_i2c_slave_init;
>> + k->event = exynos4210_i2c_slave_event;
>> + k->recv = exynos4210_i2c_slave_recv;
>> + k->send = exynos4210_i2c_slave_send;
>> +}
>> +
>> +static TypeInfo exynos4210_i2c_slave = {
>> + .name = TYPE_EXYNOS4_I2C_SLAVE,
>> + .parent = TYPE_I2C_SLAVE,
>> + .instance_size = sizeof(Exynos4210I2CSlave),
>> + .class_init = exynos4210_i2cslave_class_init,
>> +};
>> +
>> +static void exynos4210_i2c_register_types(void)
>> +{
>> + type_register_static(&exynos4210_i2c_slave);
>> + type_register_static(&exynos4210_i2c_info);
>> +}
>> +
>> +type_init(exynos4210_i2c_register_types)
> PS: This I2C implementation seems to be rather complex. Did you check
> what we wrote for S5PC110 (ah, I also learnt that Samsung renamed it to
> Exynos3210) almost three years ago
> (https://gitorious.org/samsung-linux-platform/qemu/blobs/s5pc110-stable/hw/s5pc1xx_i2c.c)?
> When I compared documentation and kernel driver sources for both devices
> I couldn't find any significant difference. Generally speaking, is it
> worth redoing everything from scratch instead of updating?
mm, it doesn't worth it, something went wrong here I think ...
--
Mitsyanko Igor
ASWG, Moscow R&D center, Samsung Electronics
email: i.mitsyanko@samsung.com
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2012-04-03 14:54 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-03-15 7:35 [Qemu-devel] [PATCH V2 0/3] Exynos: i2c, gpio and touchscreen support for NURI board Igor Mitsyanko
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 1/3] exynos4210: add Exynos4210 i2c implementation Igor Mitsyanko
2012-03-15 10:35 ` Andreas Färber
2012-03-21 11:55 ` Peter Maydell
2012-03-21 13:07 ` Igor Mitsyanko
2012-03-21 13:09 ` Peter Maydell
2012-03-21 14:18 ` Igor Mitsyanko
2012-03-21 14:24 ` Peter Maydell
2012-04-03 13:54 ` Dmitry Zhurikhin
2012-04-03 14:54 ` Igor Mitsyanko
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 2/3] exynos4210: add exynos4210 GPIO implementation Igor Mitsyanko
2012-03-15 11:06 ` Andreas Färber
2012-03-20 13:27 ` Peter Maydell
2012-03-21 13:01 ` Igor Mitsyanko
2012-03-15 7:35 ` [Qemu-devel] [PATCH V2 3/3] hw: add Atmel maxtouch touchscreen implementation Igor Mitsyanko
2012-03-15 9:48 ` Andreas Färber
2012-03-15 10:25 ` Dmitry Solodkiy
2012-03-15 10:41 ` Igor Mitsyanko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).