From a5d7eb6534a091566d63f97c8b35c0ac9623d90b Mon Sep 17 00:00:00 2001 From: balrog Date: Mon, 14 Apr 2008 21:28:11 +0000 Subject: [PATCH] Add TSC2301 touchscreen & keypad controller. Add also a facility for setting up touchscreen calibration data per-board based on calibration data generated by tslib. git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@4214 c046a42c-6fe2-441c-8c8c-71466251a162 --- console.h | 8 ++ hw/devices.h | 12 +++ hw/omap.h | 6 -- hw/tsc210x.c | 292 +++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 290 insertions(+), 28 deletions(-) diff --git a/console.h b/console.h index 1e69ffd0ba..c7f29f5073 100644 --- a/console.h +++ b/console.h @@ -32,6 +32,14 @@ void kbd_put_keycode(int keycode); void kbd_mouse_event(int dx, int dy, int dz, int buttons_state); int kbd_mouse_is_absolute(void); +struct mouse_transform_info_s { + /* Touchscreen resolution */ + int x; + int y; + /* Calibration values as used/generated by tslib */ + int a[7]; +}; + void do_info_mice(void); void do_mouse_set(int index); diff --git a/hw/devices.h b/hw/devices.h index 07c673b77d..2c3f83dc77 100644 --- a/hw/devices.h +++ b/hw/devices.h @@ -16,6 +16,18 @@ uint32_t ads7846_read(void *opaque); void ads7846_write(void *opaque, uint32_t value); struct ads7846_state_s *ads7846_init(qemu_irq penirq); +/* tsc210x.c */ +struct uwire_slave_s; +struct mouse_transform_info_s; +struct uwire_slave_s *tsc2102_init(qemu_irq pint, AudioState *audio); +struct uwire_slave_s *tsc2301_init(qemu_irq penirq, qemu_irq kbirq, + qemu_irq dav, AudioState *audio); +struct i2s_codec_s *tsc210x_codec(struct uwire_slave_s *chip); +uint32_t tsc210x_txrx(void *opaque, uint32_t value); +void tsc210x_set_transform(struct uwire_slave_s *chip, + struct mouse_transform_info_s *info); +void tsc210x_key_event(struct uwire_slave_s *chip, int key, int down); + /* stellaris_input.c */ void stellaris_gamepad_init(int n, qemu_irq *irq, const int *keycode); diff --git a/hw/omap.h b/hw/omap.h index 087b813788..91c1588e34 100644 --- a/hw/omap.h +++ b/hw/omap.h @@ -1125,10 +1125,4 @@ inline static int debug_register_io_memory(int io_index, # define cpu_register_io_memory debug_register_io_memory # endif -/* Not really omap specific, but is the only thing that uses the - uwire interface. */ -/* tsc210x.c */ -struct uwire_slave_s *tsc2102_init(qemu_irq pint, AudioState *audio); -struct i2s_codec_s *tsc210x_codec(struct uwire_slave_s *chip); - #endif /* hw_omap_h */ diff --git a/hw/tsc210x.c b/hw/tsc210x.c index 96956a47de..da2efeda89 100644 --- a/hw/tsc210x.c +++ b/hw/tsc210x.c @@ -1,12 +1,14 @@ /* * TI TSC2102 (touchscreen/sensors/audio controller) emulator. + * TI TSC2301 (touchscreen/sensors/keypad). * * Copyright (c) 2006 Andrzej Zaborowski + * Copyright (C) 2008 Nokia Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -35,12 +37,15 @@ struct tsc210x_state_s { qemu_irq pint; + qemu_irq kbint; + qemu_irq davint; QEMUTimer *timer; QEMUSoundCard card; struct uwire_slave_s chip; struct i2s_codec_s codec; uint8_t in_fifo[16384]; uint8_t out_fifo[16384]; + uint16_t model; int x, y; int pressure; @@ -64,7 +69,7 @@ struct tsc210x_state_s { uint16_t audio_ctrl1; uint16_t audio_ctrl2; uint16_t audio_ctrl3; - uint16_t pll[2]; + uint16_t pll[3]; uint16_t volume; int64_t volume_change; int softstep; @@ -78,6 +83,17 @@ struct tsc210x_state_s { int i2s_rx_rate; int i2s_tx_rate; AudioState *audio; + + int tr[8]; + + struct { + uint16_t down; + uint16_t mask; + int scan; + int debounce; + int mode; + int intr; + } kb; }; static const int resolution[4] = { 12, 8, 10, 12 }; @@ -118,17 +134,10 @@ static const uint16_t mode_regs[16] = { 0x0000, /* Y+, X- drivers */ }; -/* - * Convert screen coordinates to arbitrary values that the - * touchscreen in my Palm Tungsten E device returns. - * This shouldn't really matter (because the guest system - * should calibrate the touchscreen anyway), but let's - * imitate some real hardware. - */ -#define X_TRANSFORM(value) \ - ((3850 - ((int) (value) * (3850 - 250) / 32768)) << 4) -#define Y_TRANSFORM(value) \ - ((150 + ((int) (value) * (3037 - 150) / 32768)) << 4) +#define X_TRANSFORM(s) \ + ((s->y * s->tr[0] - s->x * s->tr[1]) / s->tr[2] + s->tr[3]) +#define Y_TRANSFORM(s) \ + ((s->y * s->tr[4] - s->x * s->tr[5]) / s->tr[6] + s->tr[7]) #define Z1_TRANSFORM(s) \ ((400 - ((s)->x >> 7) + ((s)->pressure << 10)) << 4) #define Z2_TRANSFORM(s) \ @@ -161,6 +170,7 @@ static void tsc210x_reset(struct tsc210x_state_s *s) s->audio_ctrl3 = 0x0000; s->pll[0] = 0x1004; s->pll[1] = 0x0000; + s->pll[2] = 0x1fff; s->volume = 0xffff; s->dac_power = 0x8540; s->softstep = 1; @@ -190,7 +200,15 @@ static void tsc210x_reset(struct tsc210x_state_s *s) s->i2s_tx_rate = 0; s->i2s_rx_rate = 0; + s->kb.scan = 1; + s->kb.debounce = 0; + s->kb.mask = 0x0000; + s->kb.mode = 3; + s->kb.intr = 0; + qemu_set_irq(s->pint, !s->irq); + qemu_set_irq(s->davint, !s->dav); + qemu_irq_raise(s->kbint); } struct tsc210x_rate_info_s { @@ -344,13 +362,13 @@ static uint16_t tsc2102_data_register_read(struct tsc210x_state_s *s, int reg) switch (reg) { case 0x00: /* X */ s->dav &= 0xfbff; - return TSC_CUT_RESOLUTION(X_TRANSFORM(s->x), s->precision) + + return TSC_CUT_RESOLUTION(X_TRANSFORM(s), s->precision) + (s->noise & 3); case 0x01: /* Y */ s->noise ++; s->dav &= 0xfdff; - return TSC_CUT_RESOLUTION(Y_TRANSFORM(s->y), s->precision) ^ + return TSC_CUT_RESOLUTION(Y_TRANSFORM(s), s->precision) ^ (s->noise & 3); case 0x02: /* Z1 */ @@ -364,6 +382,14 @@ static uint16_t tsc2102_data_register_read(struct tsc210x_state_s *s, int reg) (s->noise & 3); case 0x04: /* KPData */ + if ((s->model & 0xff00) == 0x2300) { + if (s->kb.intr && (s->kb.mode & 2)) { + s->kb.intr = 0; + qemu_irq_raise(s->kbint); + } + return s->kb.down; + } + return 0xffff; case 0x05: /* BAT1 */ @@ -414,9 +440,19 @@ static uint16_t tsc2102_control_register_read( return (s->pressure << 15) | ((!s->busy) << 14) | (s->nextfunction << 10) | (s->nextprecision << 8) | s->filter; - case 0x01: /* Status */ - return (s->pin_func << 14) | ((!s->enabled) << 13) | - (s->host_mode << 12) | ((!!s->dav) << 11) | s->dav; + case 0x01: /* Status / Keypad Control */ + if ((s->model & 0xff00) == 0x2100) + return (s->pin_func << 14) | ((!s->enabled) << 13) | + (s->host_mode << 12) | ((!!s->dav) << 11) | s->dav; + else + return (s->kb.intr << 15) | ((s->kb.scan || !s->kb.down) << 14) | + (s->kb.debounce << 11); + + case 0x02: /* DAC Control */ + if ((s->model & 0xff00) == 0x2300) + return s->dac_power & 0x8000; + else + goto bad_reg; case 0x03: /* Reference */ return s->ref; @@ -427,7 +463,18 @@ static uint16_t tsc2102_control_register_read( case 0x05: /* Configuration */ return s->timing; + case 0x06: /* Secondary configuration */ + if ((s->model & 0xff00) == 0x2100) + goto bad_reg; + return ((!s->dav) << 15) | ((s->kb.mode & 1) << 14) | s->pll[2]; + + case 0x10: /* Keypad Mask */ + if ((s->model & 0xff00) == 0x2100) + goto bad_reg; + return s->kb.mask; + default: + bad_reg: #ifdef TSC_VERBOSE fprintf(stderr, "tsc2102_control_register_read: " "no such register: 0x%02x\n", reg); @@ -556,10 +603,27 @@ static void tsc2102_control_register_write( s->filter = value & 0xff; return; - case 0x01: /* Status */ - s->pin_func = value >> 14; + case 0x01: /* Status / Keypad Control */ + if ((s->model & 0xff00) == 0x2100) + s->pin_func = value >> 14; + else { + s->kb.scan = (value >> 14) & 1; + s->kb.debounce = (value >> 11) & 7; + if (s->kb.intr && s->kb.scan) { + s->kb.intr = 0; + qemu_irq_raise(s->kbint); + } + } return; + case 0x02: /* DAC Control */ + if ((s->model & 0xff00) == 0x2300) { + s->dac_power &= 0x7fff; + s->dac_power |= 0x8000 & value; + } else + goto bad_reg; + break; + case 0x03: /* Reference */ s->ref = value & 0x1f; return; @@ -586,7 +650,21 @@ static void tsc2102_control_register_write( #endif return; + case 0x06: /* Secondary configuration */ + if ((s->model & 0xff00) == 0x2100) + goto bad_reg; + s->kb.mode = value >> 14; + s->pll[2] = value & 0x3ffff; + return; + + case 0x10: /* Keypad Mask */ + if ((s->model & 0xff00) == 0x2100) + goto bad_reg; + s->kb.mask = value; + return; + default: + bad_reg: #ifdef TSC_VERBOSE fprintf(stderr, "tsc2102_control_register_write: " "no such register: 0x%02x\n", reg); @@ -785,7 +863,7 @@ static void tsc210x_pin_update(struct tsc210x_state_s *s) return; } - if (!s->enabled || s->busy) + if (!s->enabled || s->busy || s->dav) return; s->busy = 1; @@ -805,6 +883,8 @@ static uint16_t tsc210x_read(struct tsc210x_state_s *s) switch (s->page) { case TSC_DATA_REGISTERS_PAGE: ret = tsc2102_data_register_read(s, s->offset); + if (!s->dav) + qemu_irq_raise(s->davint); break; case TSC_CONTROL_REGISTERS_PAGE: ret = tsc2102_control_register_read(s, s->offset); @@ -859,6 +939,22 @@ static void tsc210x_write(struct tsc210x_state_s *s, uint16_t value) } } +uint32_t tsc210x_txrx(void *opaque, uint32_t value) +{ + struct tsc210x_state_s *s = opaque; + uint32_t ret = 0; + + /* TODO: sequential reads etc - how do we make sure the host doesn't + * unintentionally read out a conversion result from a register while + * transmitting the command word of the next command? */ + if (!value || (s->state && s->command)) + ret = tsc210x_read(s); + if (value || (s->state && !s->command)) + tsc210x_write(s, value); + + return ret; +} + static void tsc210x_timer_tick(void *opaque) { struct tsc210x_state_s *s = opaque; @@ -871,6 +967,7 @@ static void tsc210x_timer_tick(void *opaque) s->busy = 0; s->dav |= mode_regs[s->function]; tsc210x_pin_update(s); + qemu_irq_lower(s->davint); } static void tsc210x_touchscreen_event(void *opaque, @@ -1001,6 +1098,7 @@ static int tsc210x_load(QEMUFile *f, void *opaque, int version_id) s->busy = qemu_timer_pending(s->timer); qemu_set_irq(s->pint, !s->irq); + qemu_set_irq(s->davint, !s->dav); return 0; } @@ -1020,9 +1118,19 @@ struct uwire_slave_s *tsc2102_init(qemu_irq pint, AudioState *audio) s->precision = s->nextprecision = 0; s->timer = qemu_new_timer(vm_clock, tsc210x_timer_tick, s); s->pint = pint; + s->model = 0x2102; s->name = "tsc2102"; s->audio = audio; + s->tr[0] = 0; + s->tr[1] = 1; + s->tr[2] = 0; + s->tr[3] = 1; + s->tr[4] = 1; + s->tr[5] = 0; + s->tr[6] = 0; + s->tr[7] = 1; + s->chip.opaque = s; s->chip.send = (void *) tsc210x_write; s->chip.receive = (void *) tsc210x_read; @@ -1048,9 +1156,149 @@ struct uwire_slave_s *tsc2102_init(qemu_irq pint, AudioState *audio) return &s->chip; } +struct uwire_slave_s *tsc2301_init(qemu_irq penirq, qemu_irq kbirq, + qemu_irq dav, AudioState *audio) +{ + struct tsc210x_state_s *s; + + s = (struct tsc210x_state_s *) + qemu_mallocz(sizeof(struct tsc210x_state_s)); + memset(s, 0, sizeof(struct tsc210x_state_s)); + s->x = 400; + s->y = 240; + s->pressure = 0; + s->precision = s->nextprecision = 0; + s->timer = qemu_new_timer(vm_clock, tsc210x_timer_tick, s); + s->pint = penirq; + s->kbint = kbirq; + s->davint = dav; + s->model = 0x2301; + s->name = "tsc2301"; + s->audio = audio; + + s->tr[0] = 0; + s->tr[1] = 1; + s->tr[2] = 0; + s->tr[3] = 1; + s->tr[4] = 1; + s->tr[5] = 0; + s->tr[6] = 0; + s->tr[7] = 1; + + s->chip.opaque = s; + s->chip.send = (void *) tsc210x_write; + s->chip.receive = (void *) tsc210x_read; + + s->codec.opaque = s; + s->codec.tx_swallow = (void *) tsc210x_i2s_swallow; + s->codec.set_rate = (void *) tsc210x_i2s_set_rate; + s->codec.in.fifo = s->in_fifo; + s->codec.out.fifo = s->out_fifo; + + tsc210x_reset(s); + + qemu_add_mouse_event_handler(tsc210x_touchscreen_event, s, 1, + "QEMU TSC2301-driven Touchscreen"); + + if (s->audio) + AUD_register_card(s->audio, s->name, &s->card); + + qemu_register_reset((void *) tsc210x_reset, s); + register_savevm(s->name, tsc2102_iid ++, 0, + tsc210x_save, tsc210x_load, s); + + return &s->chip; +} + struct i2s_codec_s *tsc210x_codec(struct uwire_slave_s *chip) { struct tsc210x_state_s *s = (struct tsc210x_state_s *) chip->opaque; return &s->codec; } + +/* + * Use tslib generated calibration data to generate ADC input values + * from the touchscreen. Assuming 12-bit precision was used during + * tslib calibration. + */ +void tsc210x_set_transform(struct uwire_slave_s *chip, + struct mouse_transform_info_s *info) +{ + struct tsc210x_state_s *s = (struct tsc210x_state_s *) chip->opaque; +#if 0 + int64_t ltr[8]; + + ltr[0] = (int64_t) info->a[1] * info->y; + ltr[1] = (int64_t) info->a[4] * info->x; + ltr[2] = (int64_t) info->a[1] * info->a[3] - + (int64_t) info->a[4] * info->a[0]; + ltr[3] = (int64_t) info->a[2] * info->a[4] - + (int64_t) info->a[5] * info->a[1]; + ltr[4] = (int64_t) info->a[0] * info->y; + ltr[5] = (int64_t) info->a[3] * info->x; + ltr[6] = (int64_t) info->a[4] * info->a[0] - + (int64_t) info->a[1] * info->a[3]; + ltr[7] = (int64_t) info->a[2] * info->a[3] - + (int64_t) info->a[5] * info->a[0]; + + /* Avoid integer overflow */ + s->tr[0] = ltr[0] >> 11; + s->tr[1] = ltr[1] >> 11; + s->tr[2] = muldiv64(ltr[2], 1, info->a[6]); + s->tr[3] = muldiv64(ltr[3], 1 << 4, ltr[2]); + s->tr[4] = ltr[4] >> 11; + s->tr[5] = ltr[5] >> 11; + s->tr[6] = muldiv64(ltr[6], 1, info->a[6]); + s->tr[7] = muldiv64(ltr[7], 1 << 4, ltr[6]); +#else + + /* This version assumes touchscreen X & Y axis are parallel or + * perpendicular to LCD's X & Y axis in some way. */ + if (abs(info->a[0]) > abs(info->a[1])) { + s->tr[0] = 0; + s->tr[1] = -info->a[6] * info->x; + s->tr[2] = info->a[0]; + s->tr[3] = -info->a[2] / info->a[0]; + s->tr[4] = info->a[6] * info->y; + s->tr[5] = 0; + s->tr[6] = info->a[4]; + s->tr[7] = -info->a[5] / info->a[4]; + } else { + s->tr[0] = info->a[6] * info->y; + s->tr[1] = 0; + s->tr[2] = info->a[1]; + s->tr[3] = -info->a[2] / info->a[1]; + s->tr[4] = 0; + s->tr[5] = -info->a[6] * info->x; + s->tr[6] = info->a[3]; + s->tr[7] = -info->a[5] / info->a[3]; + } + + s->tr[0] >>= 11; + s->tr[1] >>= 11; + s->tr[3] <<= 4; + s->tr[4] >>= 11; + s->tr[5] >>= 11; + s->tr[7] <<= 4; +#endif +} + +void tsc210x_key_event(struct uwire_slave_s *chip, int key, int down) +{ + struct tsc210x_state_s *s = (struct tsc210x_state_s *) chip->opaque; + + if (down) + s->kb.down |= 1 << key; + else + s->kb.down &= ~(1 << key); + + if (down && (s->kb.down & ~s->kb.mask) && !s->kb.intr) { + s->kb.intr = 1; + qemu_irq_lower(s->kbint); + } else if (s->kb.intr && !(s->kb.down & ~s->kb.mask) && + !(s->kb.mode & 1)) { + s->kb.intr = 0; + qemu_irq_raise(s->kbint); + } +}