From: Kuo-Jung Su <dant...@faraday-tech.com> The FTTSC010 provides two operation modes to sample the analog input voltage.
1. The manual operation mode needs to program and control the panel drivers by software step-by-step for the x-y position measurement. 2. The auto-scan mode provides a periodic sampling method to convert the analog input. This patch only implements the auto-scan mode. Signed-off-by: Kuo-Jung Su <dant...@faraday-tech.com> --- hw/arm/Makefile.objs | 1 + hw/arm/faraday_a369_soc.c | 3 + hw/arm/fttsc010.c | 264 +++++++++++++++++++++++++++++++++++++++++++++ hw/arm/fttsc010.h | 39 +++++++ 4 files changed, 307 insertions(+) create mode 100644 hw/arm/fttsc010.c create mode 100644 hw/arm/fttsc010.h diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs index 6f3bf2d..9fdefc8 100644 --- a/hw/arm/Makefile.objs +++ b/hw/arm/Makefile.objs @@ -50,3 +50,4 @@ obj-y += fti2c010.o obj-y += ftssp010.o obj-y += ftgmac100.o obj-y += ftlcdc200.o +obj-y += fttsc010.o diff --git a/hw/arm/faraday_a369_soc.c b/hw/arm/faraday_a369_soc.c index da89dfa..386a1a8 100644 --- a/hw/arm/faraday_a369_soc.c +++ b/hw/arm/faraday_a369_soc.c @@ -279,6 +279,9 @@ a369soc_device_init(FaradaySoCState *s) s->pic[23], /* FIFO Under-Run */ s->pic[22], /* AHB Bus Error */ NULL); + + /* fttsc010 */ + sysbus_create_simple("fttsc010", 0x92400000, s->pic[19]); } static int a369soc_init(SysBusDevice *dev) diff --git a/hw/arm/fttsc010.c b/hw/arm/fttsc010.c new file mode 100644 index 0000000..44c43e0 --- /dev/null +++ b/hw/arm/fttsc010.c @@ -0,0 +1,264 @@ +/* + * Faraday FTTSC010 emulator. + * + * Copyright (c) 2012 Faraday Technology + * Written by Dante Su <dant...@faraday-tech.com> + * + * This code is licensed under GNU GPL v2+. + */ + +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "hw/devices.h" +#include "ui/console.h" +#include "qemu/timer.h" +#include "sysemu/sysemu.h" + +#include "faraday.h" +#include "fttsc010.h" + +#define X_AXIS_DMAX 3470 +#define X_AXIS_MIN 290 +#define Y_AXIS_DMAX 3450 +#define Y_AXIS_MIN 200 + +#define ADS_XPOS(x, y) \ + (X_AXIS_MIN + ((X_AXIS_DMAX * (x)) >> 15)) +#define ADS_YPOS(x, y) \ + (Y_AXIS_MIN + ((Y_AXIS_DMAX * (y)) >> 15)) +#define ADS_Z1POS(x, y) \ + (8) +#define ADS_Z2POS(x, y) \ + ((1600 + ADS_XPOS(x, y)) * ADS_Z1POS(x, y) / ADS_XPOS(x, y)) + +#define TYPE_FTTSC010 "fttsc010" + +#define CFG_REGSIZE (0x3c / 4) + +typedef struct Fttsc010State { + SysBusDevice busdev; + MemoryRegion iomem; + qemu_irq irq; + + uint64_t interval; + QEMUTimer *qtimer; + + int x, y; + int z1, z2; + uint32_t freq; + + /* HW registers */ + uint32_t regs[CFG_REGSIZE]; +} Fttsc010State; + +#define FTTSC010(obj) \ + OBJECT_CHECK(Fttsc010State, obj, TYPE_FTTSC010) + +#define TSC_REG32(s, off) \ + ((s)->regs[(off) / 4]) + +static void fttsc010_update_irq(Fttsc010State *s) +{ + qemu_set_irq(s->irq, !!(TSC_REG32(s, REG_IMR) & TSC_REG32(s, REG_ISR))); +} + +static uint64_t +fttsc010_mem_read(void *opaque, hwaddr addr, unsigned size) +{ + uint32_t ret = 0; + Fttsc010State *s = FTTSC010(opaque); + + switch (addr) { + case REG_CR ... REG_DCR: + ret = s->regs[addr / 4]; + break; + case REG_XYR: + ret = deposit32(ret, 0, 12, s->x); + ret = deposit32(ret, 16, 12, s->y); + break; + case REG_ZR: + ret = deposit32(ret, 0, 12, s->z1); + ret = deposit32(ret, 16, 12, s->z2); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "fttsc010: undefined memory access@%#" HWADDR_PRIx "\n", addr); + break; + } + + return ret; +} + +static void +fttsc010_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) +{ + uint32_t dly, sdiv, mdiv; + Fttsc010State *s = FTTSC010(opaque); + + switch (addr) { + case REG_CR: + TSC_REG32(s, REG_CR) = (uint32_t)val; + if (TSC_REG32(s, REG_CR) & (CR_AS | CR_RD1)) { + /* ADC conversion delay with frame number */ + dly = extract32(TSC_REG32(s, REG_DCR), 0, 16); + /* ADC sample clock divider */ + sdiv = extract32(TSC_REG32(s, REG_CSR), 8, 8); + /* ADC main clock divider */ + mdiv = extract32(TSC_REG32(s, REG_CSR), 0, 8); + /* Calculate sample rate/timer interval */ + s->interval = s->freq / ((mdiv + 1) * (sdiv + 1) * (dly + 1) * 64); + s->interval = MAX(1ULL, s->interval); + qemu_mod_timer(s->qtimer, + s->interval + qemu_get_clock_ms(vm_clock)); + } else { + qemu_del_timer(s->qtimer); + } + break; + case REG_ISR: + TSC_REG32(s, REG_ISR) &= ~((uint32_t)val); + fttsc010_update_irq(s); + break; + case REG_IMR: + TSC_REG32(s, REG_IMR) = (uint32_t)val; + fttsc010_update_irq(s); + break; + case REG_CSR ... REG_DCR: + s->regs[addr / 4] = (uint32_t)val; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "fttsc010: undefined memory access@%#" HWADDR_PRIx "\n", addr); + break; + } +} + +static const MemoryRegionOps mmio_ops = { + .read = fttsc010_mem_read, + .write = fttsc010_mem_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + } +}; + +static void fttsc010_timer_tick(void *opaque) +{ + Fttsc010State *s = FTTSC010(opaque); + + /* update isr for auto-scan */ + if (TSC_REG32(s, REG_CR) & CR_AS) { + TSC_REG32(s, REG_ISR) |= ISR_AS; + } + + /* turn it off, when it's under one-shot mode */ + TSC_REG32(s, REG_CR) &= ~CR_RD1; + + /* update irq signal */ + fttsc010_update_irq(s); + + /* reschedule for next auto-scan */ + if (TSC_REG32(s, REG_CR) & CR_AS) { + qemu_mod_timer(s->qtimer, s->interval + qemu_get_clock_ms(vm_clock)); + } +} + +static void +fttsc010_touchscreen_event(void *opaque, int x, int y, int z, int bt) +{ + Fttsc010State *s = FTTSC010(opaque); + + if (bt) { + /* button pressed */ + x = 0x7fff - x; + s->x = ADS_XPOS(x, y); + s->y = ADS_YPOS(x, y); + s->z1 = ADS_Z1POS(x, y); + s->z2 = ADS_Z2POS(x, y); + } else { + /* button released */ + s->z1 = 0; + s->z2 = 0; + } +} + +static void fttsc010_reset(DeviceState *ds) +{ + Fttsc010State *s = FTTSC010(SYS_BUS_DEVICE(ds)); + + memset(s->regs, 0, sizeof(s->regs)); + TSC_REG32(s, REG_REVR) = 0x00010000; /* rev. 1.0.0 */ + + /* initialize touch sample interval to 10 ms */ + TSC_REG32(s, REG_CSR) = CSR_SDIV(16) | CSR_MDIV(5); + TSC_REG32(s, REG_DCR) = DCR_DLY(100); + + s->x = 0; + s->y = 0; + s->z1 = 0; + s->z2 = 0; + + qemu_set_irq(s->irq, 0); +} + +static int fttsc010_init(SysBusDevice *dev) +{ + Fttsc010State *s = FTTSC010(dev); + + s->qtimer = qemu_new_timer_ms(vm_clock, fttsc010_timer_tick, s); + + memory_region_init_io(&s->iomem, + &mmio_ops, + s, + TYPE_FTTSC010, + 0x1000); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->irq); + + qemu_add_mouse_event_handler(fttsc010_touchscreen_event, s, 1, + "QEMU FTTSC010-driven Touchscreen"); + + return 0; +} + +static const VMStateDescription vmstate_fttsc010 = { + .name = TYPE_FTTSC010, + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, Fttsc010State, CFG_REGSIZE), + VMSTATE_END_OF_LIST(), + } +}; + +static Property properties_fttsc010[] = { + DEFINE_PROP_UINT32("freq", Fttsc010State, freq, 66000000), + DEFINE_PROP_END_OF_LIST(), +}; + +static void fttsc010_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + k->init = fttsc010_init; + dc->reset = fttsc010_reset; + dc->vmsd = &vmstate_fttsc010; + dc->props = properties_fttsc010; + dc->no_user = 1; +} + +static const TypeInfo fttsc010_info = { + .name = TYPE_FTTSC010, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Fttsc010State), + .class_init = fttsc010_class_init, +}; + +static void fttsc010_register_types(void) +{ + type_register_static(&fttsc010_info); +} + +type_init(fttsc010_register_types) diff --git a/hw/arm/fttsc010.h b/hw/arm/fttsc010.h new file mode 100644 index 0000000..b600645 --- /dev/null +++ b/hw/arm/fttsc010.h @@ -0,0 +1,39 @@ +/* + * Faraday FTTSC010 touchscreen driver + * + * Copyright (C) 2012 Faraday Technology + * Written by Dante Su <dant...@faraday-tech.com> + * + * This file is licensed under GNU GPL v2+. + */ + +#ifndef HW_ARM_FTTSC010_H +#define HW_ARM_FTTSC010_H + +#include "qemu/bitops.h" + +#define REG_CR 0x00 /* Control Register */ +#define REG_ISR 0x04 /* Interrupt Status Register */ +#define REG_IMR 0x08 /* Interrupt Mask Register */ +#define REG_REVR 0x0C /* Revision Register */ +#define REG_CSR 0x30 /* Clock & Sample Rate Register */ +#define REG_PFR 0x34 /* Panel Function Register */ +#define REG_DCR 0x38 /* Delay Control Register */ +#define REG_XYR 0x3C /* Touchscreen X,Y-Axis Register */ +#define REG_ZR 0x4C /* Touchscreen Z-Axis (Pressure) Register */ + +#define CR_AS BIT(31) /* Auto-scan enable */ +#define CR_RD1 BIT(30) /* Single read enabled */ +#define CR_CHDLY(x) BIT(16 + (x % 7)) /* ADC channel x delay enabled */ +#define CR_APWRDN BIT(9) /* ADC auto power down mode */ +#define CR_PWRDN BIT(8) /* ADC and IPGA power down */ +#define CR_ICS(x) ((x) & 0xf) /* ADC analog input channel select */ + +#define CSR_SDIV(x) (((x) & 0xff) << 8) /* ADC sample clock divider */ +#define CSR_MDIV(x) (((x) & 0xff) << 0) /* ADC main clock divider */ + +#define DCR_DLY(x) ((x) & 0xffff) /* ADC conversion delay */ + +#define ISR_AS BIT(10) /* Auto-scan cpmplete */ + +#endif -- 1.7.9.5