From d829075a0003fd5a976d5e2303b0317d3dc63ea8 Mon Sep 17 00:00:00 2001 From: Mark Arigo Date: Sun, 24 Feb 2008 04:12:16 +0000 Subject: Driver for the Synaptics touchpad on the m:robe 100 based on the 3-wire interface spec. Needs some tweaking as it's too sensitive with the default hardware settings. For now, the vertical strip is divided into up/select/down buttons. Also, redo the keymap (using the Gigabeat as a starting point), but it still needs a good bit of work. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@16400 a1c6a512-1295-4272-9138-f99709370657 --- .../target/arm/olympus/mrobe-100/button-mr100.c | 564 ++++++++++++++++++++- .../target/arm/olympus/mrobe-100/button-target.h | 39 +- firmware/target/arm/system-pp502x.c | 8 + 3 files changed, 588 insertions(+), 23 deletions(-) (limited to 'firmware/target/arm') diff --git a/firmware/target/arm/olympus/mrobe-100/button-mr100.c b/firmware/target/arm/olympus/mrobe-100/button-mr100.c index 49561cb8d7..43db2c5f6d 100644 --- a/firmware/target/arm/olympus/mrobe-100/button-mr100.c +++ b/firmware/target/arm/olympus/mrobe-100/button-mr100.c @@ -27,14 +27,562 @@ #include "backlight-target.h" #include "system.h" -void button_init_device(void) +#define LOGF_ENABLE +#include "logf.h" + +/* Driver for the Synaptics Touchpad based on the "Synaptics Modular Embedded + Protocol: 3-Wire Interface Specification" documentation */ + +#define ACK (GPIOD_INPUT_VAL & 0x1) +#define ACK_HI GPIOD_OUTPUT_VAL |= 0x1 +#define ACK_LO GPIOD_OUTPUT_VAL &= ~0x1 + +#define CLK ((GPIOD_INPUT_VAL & 0x2) >> 1) +#define CLK_HI GPIOD_OUTPUT_VAL |= 0x2 +#define CLK_LO GPIOD_OUTPUT_VAL &= ~0x2 + +#define DATA ((GPIOD_INPUT_VAL & 0x4) >> 2) +#define DATA_HI GPIOD_OUTPUT_EN |= 0x4; GPIOD_OUTPUT_VAL |= 0x4 +#define DATA_LO GPIOD_OUTPUT_EN |= 0x4; GPIOD_OUTPUT_VAL &= ~0x4 + +#define LO 0 +#define HI 1 + +#define STATUS_READY 1 +#define READ_RETRY 8 +#define READ_ERROR -1 + +#define HELLO_HEADER 0x19 +#define HELLO_ID 0x1 +#define BUTTONS_HEADER 0x1a +#define BUTTONS_ID 0x9 +#define ABSOLUTE_HEADER 0x0b + +static int syn_status = 0; +static int int_btn = BUTTON_NONE; + +static int syn_wait_clk_change(unsigned int val) +{ + int i; + + for (i = 0; i < 10000; i++) + { + if (CLK == val) + return 1; + } + + return 0; +} + +static inline int syn_get_data(void) +{ + GPIOD_OUTPUT_EN &= ~0x4; + return DATA; +} + +static void syn_wait_guest_flush(void) +{ + /* Flush receiving (flushee) state: + handshake until DATA goes high during P3 stage */ + if (CLK == LO) + { + ACK_HI; /* P1 -> P2 */ + syn_wait_clk_change(HI); /* P2 -> P3 */ + } + + while (syn_get_data() == LO) + { + ACK_HI; /* P3 -> P0 */ + syn_wait_clk_change(LO); /* P0 -> P1 */ + ACK_LO; /* P1 -> P2 */ + syn_wait_clk_change(HI); /* P2 -> P3 */ + } + + /* Continue handshaking until back to P0 */ + ACK_HI; /* P3 -> P0 */ +} + +static void syn_flush(void) +{ + int i; + + logf("syn_flush..."); + + /* Flusher holds DATA low for at least 36 handshake cycles */ + DATA_LO; + + for (i = 0; i < 36; i++) + { + syn_wait_clk_change(LO); /* P0 -> P1 */ + ACK_LO; /* P1 -> P2 */ + syn_wait_clk_change(HI); /* P2 -> P3 */ + ACK_HI; /* P3 -> P0 */ + } + + /* Raise DATA in P1 stage */ + syn_wait_clk_change(LO); /* P0 -> P1 */ + DATA_HI; + + /* After a flush, the flushing device enters a flush-receiving (flushee) + state */ + syn_wait_guest_flush(); +} + +static int syn_send_data(int *data, int len) +{ + int i, bit; + int parity = 0; + + logf("syn_send_data..."); + + /* 1. Lower DATA line to issue a request-to-send to guest */ + DATA_LO; + + /* 2. Wait for guest to lower CLK */ + syn_wait_clk_change(LO); + + /* 3. Lower ACK (with DATA still low) */ + ACK_LO; + + /* 4. Wait for guest to raise CLK */ + syn_wait_clk_change(HI); + + /* 5. Send data */ + for (i = 0; i < len; i++) + { + logf(" sending byte: %d", data[i]); + + bit = 0; + while (bit < 8) + { + /* 5a. Drive data low if bit is 0, or high if bit is 1 */ + if (data[i] & (1 << bit)) + { + DATA_HI; + parity++; + } + else + { + DATA_LO; + } + bit++; + + /* 5b. Invert ACK to indicate that the data bit is ready */ + ACK_HI; + + /* 5c. Wait for guest to invert CLK */ + syn_wait_clk_change(LO); + + /* Repeat for next bit */ + if (data[i] & (1 << bit)) + { + DATA_HI; + parity++; + } + else + { + DATA_LO; + } + bit++; + + ACK_LO; + + syn_wait_clk_change(HI); + } + } + + /* 7. Transmission termination sequence: */ + /* 7a. Host may put parity bit on DATA. Hosts that do not generate + parity should set DATA high. Parity is 1 if there's an odd + number of '1' bits, or 0 if there's an even number of '1' bits. */ + parity = parity % 2; + logf(" send parity = %d", parity); + if (parity) + { + DATA_HI; + } + else + { + DATA_LO; + } + + /* 7b. Raise ACK to indicate that the optional parity bit is ready */ + ACK_HI; + + /* 7c. Guest lowers CLK */ + syn_wait_clk_change(LO); + + /* 7d. Pull DATA high (if parity bit was 0) */ + DATA_HI; + + /* 7e. Lower ACK to indicate that the stop bit is ready */ + ACK_LO; + + /* 7f. Guest raises CLK */ + syn_wait_clk_change(HI); + + /* 7g. If DATA is low, guest is flushing this transfer. Host should + enter the flushee state. */ + if (syn_get_data() == LO) + { + logf(" module flushing"); + syn_wait_guest_flush(); + return -1; + } + + /* 7h. Host raises ACK and the link enters the idle state */ + ACK_HI; + + return len; +} + +static int syn_read_data(int *data, int data_len) { - /* taken from the mr-100 bootloader (offset 0x1e72) */ - //~ DEV_EN |= 0x20000; /* enable the touchpad ?? */ + int i, len, bit, parity, tmp; + int *data_ptr; + + logf("syn_read_data..."); + + /* 1. Guest drives CLK low */ + if (CLK != LO) + return 0; + + /* 1a. If the host is willing to receive a packet it lowers ACK */ + ACK_LO; + + /* 2. Guest may issue a request-to-send by lowering DATA. If the + guest decides not to transmit a packet, it may abort the + transmission by not lowering DATA. */ + + /* 3. The guest raises CLK */ + syn_wait_clk_change(HI); + + /* 4. If the guest is still driving DATA low, the transfer is commited + to occur. Otherwise, the transfer is aborted. In either case, + the host raises ACK. */ + if (syn_get_data() == HI) + { + logf(" read abort"); + + ACK_HI; + return READ_ERROR; + } + else + { + ACK_HI; + } + + /* 5. Read the incoming data packet */ + i = 0; + len = 0; + parity = 0; + while (i <= len) + { + bit = 0; + + if (i < data_len) + data_ptr = &data[i]; + else + data_ptr = &tmp; + + *data_ptr = 0; + while (bit < 8) + { + /* 5b. Guset inverts CLK to indicate that data is ready */ + syn_wait_clk_change(LO); + + /* 5d. Read the data bit from DATA */ + if (syn_get_data() == HI) + { + *data_ptr |= (1 << bit); + parity++; + } + bit++; + /* 5e. Invert ACK to indicate that data has been read */ + ACK_LO; + + /* Repeat for next bit */ + syn_wait_clk_change(HI); + + if (syn_get_data() == HI) + { + *data_ptr |= (1 << bit); + parity++; + } + bit++; + + ACK_HI; + } + + /* First byte is the packet header */ + if (i == 0) + { + /* Format control (bit 3) should be 1 */ + if (*data_ptr & 0x8) + { + /* Packet length is bits 0:2 */ + len = *data_ptr & 0x7; + logf(" packet length = %d", len); + } + else + { + logf(" invalid format ctrl bit"); + return READ_ERROR; + } + } + + i++; + } + + /* 7. Transmission termination cycle */ + /* 7a. The guest generates a parity bit on DATA */ + /* 7b. The host waits for guest to lower CLK */ + syn_wait_clk_change(LO); + + /* 7c. The host verifies the parity bit is correct */ + parity = parity % 2; + logf(" parity check: %d / %d", syn_get_data(), parity); + /* TODO: parity error handling */ + + /* 7d. The host lowers ACK */ + ACK_LO; + + /* 7e. The host waits for the guest to raise CLK indicating + that the stop bit is ready */ + syn_wait_clk_change(HI); + + /* 7f. The host reads DATA and verifies that it is 1 */ + if (syn_get_data() == LO) + { + logf(" framing error"); + + ACK_HI; + return READ_ERROR; + } + + ACK_HI; + + return len; +} + +static int syn_read_device(int *data, int len) +{ + int i; + int ret = READ_ERROR; + + for (i = 0; i < READ_RETRY; i++) + { + if (syn_wait_clk_change(LO)) + { + /* module is sending data */ + ret = syn_read_data(data, len); + if (ret != READ_ERROR) + return ret; + + syn_flush(); + } + else + { + /* module is idle */ + return 0; + } + } + + return ret; +} + +static int syn_reset(void) +{ + int val, id; + int data[2]; + + logf("syn_reset..."); + + /* reset module 0 */ + val = (0 << 4) | (1 << 3) | 0; + syn_send_data(&val, 1); + + val = syn_read_device(data, 2); + if (val == 1) + { + val = data[0] & 0xff; /* packet header */ + id = (data[1] >> 4) & 0xf; /* packet id */ + if ((val == HELLO_HEADER) && (id == HELLO_ID)) + { + logf(" module 0 reset"); + return 1; + } + } + + logf(" reset failed"); + return 0; +} + +#if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE) +static void syn_info(void) +{ + int i, val; + int data[8]; + + logf("syn_info..."); + + /* module base info */ + logf("module base info:"); + data[0] = (0 << 4) | (0 << 3) | 1; + data[1] = 0x80; + syn_send_data(data, 2); + val = syn_read_device(data, 8); + if (val > 0) + { + for (i = 0; i < 8; i++) + logf(" data[%d] = 0x%02x", i, data[i]); + } + + /* module product info */ + logf("module product info:"); + data[0] = (0 << 4) | (0 << 3) | 1; + data[1] = 0x81; + syn_send_data(data, 2); + val = syn_read_device(data, 8); + if (val > 0) + { + for (i = 0; i < 8; i++) + logf(" data[%d] = 0x%02x", i, data[i]); + } + + /* module serialization */ + logf("module serialization:"); + data[0] = (0 << 4) | (0 << 3) | 1; + data[1] = 0x82; + syn_send_data(data, 2); + val = syn_read_device(data, 8); + if (val > 0) + { + for (i = 0; i < 8; i++) + logf(" data[%d] = 0x%02x", i, data[i]); + } + + /* 1-D sensor info */ + logf("1-d sensor info:"); + data[0] = (0 << 4) | (0 << 3) | 1; + data[1] = 0x80 + 0x20; + syn_send_data(data, 2); + val = syn_read_device(data, 8); + if (val > 0) + { + for (i = 0; i < 8; i++) + logf(" data[%d] = 0x%02x", i, data[i]); + } +} +#endif + +void button_init_device(void) +{ /* enable touchpad leds */ - GPIOA_ENABLE |= 0xff; + GPIOA_ENABLE |= BUTTONLIGHT_ALL; GPIOA_OUTPUT_EN |= BUTTONLIGHT_ALL; + + /* enable touchpad */ + GPO32_ENABLE |= 0x40000000; + GPO32_VAL &= ~0x40000000; + + /* enable ACK, CLK, DATA lines */ + GPIOD_ENABLE |= (0x1 | 0x2 | 0x4); + + GPIOD_OUTPUT_EN |= 0x1; /* ACK */ + GPIOD_OUTPUT_VAL |= 0x1; /* high */ + + GPIOD_OUTPUT_EN &= ~0x2; /* CLK */ + + GPIOD_OUTPUT_EN |= 0x4; /* DATA */ + GPIOD_OUTPUT_VAL |= 0x4; /* high */ + + syn_flush(); + + if (syn_reset()) + { +#if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE) + syn_info(); +#endif + + syn_status = STATUS_READY; + + /* enable interrupts */ + GPIOD_INT_LEV &= ~0x2; + GPIOD_INT_CLR |= 0x2; + GPIOD_INT_EN |= 0x2; + + CPU_INT_EN |= HI_MASK; + CPU_HI_INT_EN |= GPIO0_MASK; + } +} + +/* + * Button interrupt handler + */ +void button_int(void) +{ + int data[4]; + int val, id; + + int_btn = BUTTON_NONE; + + if (syn_status == STATUS_READY) + { + /* disable interrupt while we read the touchpad */ + GPIOD_INT_EN &= ~0x2; + + val = syn_read_device(data, 4); + if (val > 0) + { + val = data[0] & 0xff; /* packet header */ + id = (data[1] >> 4) & 0xf; /* packet id */ + + logf("button_read_device..."); + logf(" data[0] = 0x%08x", data[0]); + logf(" data[1] = 0x%08x", data[1]); + logf(" data[2] = 0x%08x", data[2]); + logf(" data[3] = 0x%08x", data[3]); + + if ((val == BUTTONS_HEADER) && (id == BUTTONS_ID)) + { + /* Buttons packet - touched one of the 5 "buttons" */ + if (data[1] & 0x1) + int_btn |= BUTTON_PLAY; + if (data[1] & 0x2) + int_btn |= BUTTON_MENU; + if (data[1] & 0x4) + int_btn |= BUTTON_LEFT; + if (data[1] & 0x8) + int_btn |= BUTTON_DISPLAY; + if (data[2] & 0x1) + int_btn |= BUTTON_RIGHT; + + /* An Absolute packet should follow which we ignore */ + val = syn_read_device(data, 4); + + logf(" int_btn = 0x%04x", int_btn); + } + else if (val == ABSOLUTE_HEADER) + { + /* Absolute packet - the finger is on the vertical strip. + Position ranges from 1-4095, with 1 at the bottom. */ + val = ((data[1] >> 4) << 8) | data[2]; /* position */ + if ((val > 0) && (val <= 1365)) + int_btn |= BUTTON_DOWN; + else if ((val > 1365) && (val <= 2730)) + int_btn |= BUTTON_SELECT; + else if ((val > 2730) && (val <= 4095)) + int_btn |= BUTTON_UP; + } + } + + /* re-enable interrupts */ + GPIOD_INT_LEV &= ~0x2; + GPIOD_INT_CLR |= 0x2; + GPIOD_INT_EN |= 0x2; + } } /* @@ -42,11 +590,11 @@ void button_init_device(void) */ int button_read_device(void) { - int btn = BUTTON_NONE; - - if(~GPIOA_INPUT_VAL & 0x40) + int btn = int_btn; + + if (~GPIOA_INPUT_VAL & 0x40) btn |= BUTTON_POWER; - + return btn; } diff --git a/firmware/target/arm/olympus/mrobe-100/button-target.h b/firmware/target/arm/olympus/mrobe-100/button-target.h index c7d9114004..78b37f6ba6 100644 --- a/firmware/target/arm/olympus/mrobe-100/button-target.h +++ b/firmware/target/arm/olympus/mrobe-100/button-target.h @@ -28,6 +28,7 @@ bool button_hold(void); void button_init_device(void); int button_read_device(void); +void button_int(void); #define POWEROFF_BUTTON BUTTON_POWER #define POWEROFF_COUNT 10 @@ -36,25 +37,33 @@ int button_read_device(void); for the H10 keypad & remote. THESE ARE NOT CORRECT! */ /* Main unit's buttons */ -#define BUTTON_POWER 0x00000001 -#define BUTTON_LEFT 0x00000002 -#define BUTTON_RIGHT 0x00000004 -#define BUTTON_REW 0x00000008 -#define BUTTON_PLAY 0x00000010 -#define BUTTON_FF 0x00000020 -#define BUTTON_SCROLL_UP 0x00000040 -#define BUTTON_SCROLL_DOWN 0x00000080 -#define BUTTON_MAIN (BUTTON_POWER|BUTTON_O|BUTTON_BACK|BUTTON_REW\ - |BUTTON_PLAY|BUTTON_FF) +#define BUTTON_PLAY 0x00000001 +#define BUTTON_MENU 0x00000002 +#define BUTTON_LEFT 0x00000004 +#define BUTTON_DISPLAY 0x00000008 +#define BUTTON_RIGHT 0x00000010 +#define BUTTON_SELECT 0x00000020 +#define BUTTON_UP 0x00000040 +#define BUTTON_SLIDE_UP 0x00000080 +#define BUTTON_DOWN 0x00000100 +#define BUTTON_SLIDE_DOWN 0x00000200 +#define BUTTON_POWER 0x00000400 +#define BUTTON_MAIN (BUTTON_PLAY|BUTTON_MENU|BUTTON_LEFT|BUTTON_DISPLAY\ + |BUTTON_RIGHT|BUTTON_SELECT|BUTTON_UP|BUTTON_SLIDE_UP\ + |BUTTON_DOWN|BUTTON_SLIDE_DOWN|BUTTON_POWER) /* Remote control's buttons */ -#define BUTTON_RC_REW 0x00080000 -#define BUTTON_RC_PLAY 0x00100000 -#define BUTTON_RC_FF 0x00200000 +#define BUTTON_RC_PLAY 0x00010000 +#define BUTTON_RC_REW 0x00020000 +#define BUTTON_RC_FF 0x00040000 +#define BUTTON_RC_DISPLAY 0x00080000 +#define BUTTON_RC_FAV 0x00100000 +#define BUTTON_RC_MODE 0x00200000 #define BUTTON_RC_VOL_UP 0x00400000 #define BUTTON_RC_VOL_DOWN 0x00800000 -#define BUTTON_REMOTE (BUTTON_RC_PLAY|BUTTON_RC_VOL_UP|BUTTON_RC_VOL_DOWN\ - |BUTTON_RC_REW|BUTTON_RC_FF) +#define BUTTON_REMOTE (BUTTON_RC_PLAY|BUTTON_RC_REW|BUTTON_RC_FF|\ + |BUTTON_RC_DISPLAY|BUTTON_RC_FAV|BUTTON_RC_MODE\ + |BUTTON_RC_VOL_UP|BUTTON_RC_VOL_DOWN) #define RC_POWEROFF_BUTTON BUTTON_RC_PLAY #endif /* _BUTTON_TARGET_H_ */ diff --git a/firmware/target/arm/system-pp502x.c b/firmware/target/arm/system-pp502x.c index 0b5e9e1c13..3dd802a3ed 100644 --- a/firmware/target/arm/system-pp502x.c +++ b/firmware/target/arm/system-pp502x.c @@ -35,6 +35,9 @@ extern void microsd_int(void); /* Sansa E200 and C200 */ extern void button_int(void); extern void clickwheel_int(void); #endif +#ifdef MROBE_100 +extern void button_int(void); +#endif void irq(void) { @@ -66,6 +69,11 @@ void irq(void) if (GPIOL_INT_STAT & 0x08) microsd_int(); } +#elif defined(MROBE_100) + else if (CPU_HI_INT_STAT & GPIO0_MASK) { + if (GPIOD_INT_STAT & 0x2) + button_int(); + } #endif #ifdef HAVE_USBSTACK else if (CPU_INT_STAT & USB_MASK) { -- cgit