Skip to content

Instantly share code, notes, and snippets.

@pamaury
Created December 26, 2011 17:42
Show Gist options
  • Save pamaury/1521701 to your computer and use it in GitHub Desktop.
Save pamaury/1521701 to your computer and use it in GitHub Desktop.
"Natural" Touchpad Driver
diff --git a/firmware/export/config/sansafuzeplus.h b/firmware/export/config/sansafuzeplus.h
index 880bbae..f16f9f1 100644
--- a/firmware/export/config/sansafuzeplus.h
+++ b/firmware/export/config/sansafuzeplus.h
@@ -180,7 +180,7 @@
#define USE_ROCKBOX_USB
#define USB_VENDOR_ID 0x0781
#define USB_PRODUCT_ID 0x74e1
-#define HAVE_USB_HID_MOUSE
+#define HAVE_USB_HID_TOUCHPAD
#define HAVE_BOOTLOADER_USB_MODE
/* The fuze+ actually interesting partition table does not use 512-byte sector
diff --git a/firmware/target/arm/imx233/sansa-fuzeplus/button-fuzeplus.c b/firmware/target/arm/imx233/sansa-fuzeplus/button-fuzeplus.c
index 0250d27..41a2a97 100644
--- a/firmware/target/arm/imx233/sansa-fuzeplus/button-fuzeplus.c
+++ b/firmware/target/arm/imx233/sansa-fuzeplus/button-fuzeplus.c
@@ -199,9 +199,40 @@ static struct button_area_t button_areas[] =
#define RMI_INTERRUPT 1
+enum touchpad_mode_t
+{
+ /* Two fingers reported, locked until no finger touch */
+ MODE_TWO_FINGERS,
+ /* One finger report, report touch after a while and wait any transition */
+ MODE_BUTTON_WAIT,
+ /* One finger sliding, don't interpret touch */
+ MODE_SLIDING,
+ /* No finger reported */
+ MODE_IDLE
+};
+
+/* Delay before interpreting a single touch as a button. Any move during
+ * that time will be interpreted as sliding */
+#define TOUCHPAD_BUTTON_DELAY (HZ / 10)
+/* Normal delay when no action is detected (useful for low power mode) */
+#define TOUCHPAD_IDLE_DELAY HZ
+/* Arrow subdivision */
+#define TOUCHPAD_ARROW_SUBDIV 4
+/* Minimum arrow distance before sliding */
+#define TOUCHPAD_SLIDING_THRESH 2
+
+/* touchpad button bitmap for button_read_device() */
static int touchpad_btns = 0;
-static bool two_fingers_mode = 0;
-static int button_delay = 0;
+static enum touchpad_mode_t touchpad_mode = MODE_IDLE;
+/* tick when the key was first hold */
+static int button_first_tick = 0;
+static int button_detected = 0;
+static int next_action_tick = 0;
+static bool button_arrow = false;
+static int button_arrow_x = 0;
+static int button_arrow_y = 0;
+static int sliding_button = 0;
+
static long rmi_stack [DEFAULT_STACK_SIZE/sizeof(long)];
static const char rmi_thread_name[] = "rmi";
static struct event_queue rmi_queue;
@@ -218,6 +249,54 @@ static int find_button(int x, int y)
return 0;
}
+static bool is_button_arrow(int button)
+{
+ return button == BUTTON_LEFT || button == BUTTON_RIGHT ||
+ button == BUTTON_DOWN || button == BUTTON_UP || button == BUTTON_SELECT;
+}
+
+static void get_arrow_pos(int x, int y, int *ar_x, int *ar_y)
+{
+ *ar_x = (x - 1500) / TOUCHPAD_ARROW_SUBDIV;
+ *ar_y = (y - 1500) / TOUCHPAD_ARROW_SUBDIV;
+}
+
+static int find_sliding(int old_x, int old_y, int new_x, int new_y)
+{
+ if(new_x <= old_x - TOUCHPAD_SLIDING_THRESH)
+ return BUTTON_LEFT;
+ else if(new_x >= old_x + TOUCHPAD_SLIDING_THRESH)
+ return BUTTON_RIGHT;
+ else if(new_y <= old_y - TOUCHPAD_SLIDING_THRESH)
+ return BUTTON_DOWN;
+ else if(new_y >= old_y + TOUCHPAD_SLIDING_THRESH)
+ return BUTTON_UP;
+ else
+ return 0;
+}
+
+static int update_sliding(int old_x, int old_y, int new_x, int new_y, int sliding_button)
+{
+ if(sliding_button == BUTTON_LEFT || sliding_button == BUTTON_RIGHT)
+ {
+ if(new_x <= old_x - TOUCHPAD_SLIDING_THRESH)
+ return BUTTON_LEFT;
+ else if(new_x >= old_x + TOUCHPAD_SLIDING_THRESH)
+ return BUTTON_RIGHT;
+ else
+ return 0;
+ }
+ else
+ {
+ if(new_y <= old_y - TOUCHPAD_SLIDING_THRESH)
+ return BUTTON_DOWN;
+ else if(new_y >= old_y + TOUCHPAD_SLIDING_THRESH)
+ return BUTTON_UP;
+ else
+ return 0;
+ }
+}
+
static int touchpad_read_device(void)
{
return touchpad_btns;
@@ -232,13 +311,17 @@ static void rmi_attn_cb(int bank, int pin)
queue_post(&rmi_queue, RMI_INTERRUPT, 0);
}
+#define ABS(x) ((x) < 0 ? -(x) : (x))
+
static void rmi_thread(void)
{
struct queue_event ev;
-
+
+ next_action_tick = current_tick + TOUCHPAD_IDLE_DELAY;
while(1)
{
- queue_wait(&rmi_queue, &ev);
+ queue_wait_w_tmo(&rmi_queue, &ev, next_action_tick - current_tick);
+ next_action_tick = current_tick + TOUCHPAD_IDLE_DELAY;
/* handle usb connect and ignore all messages except rmi interrupts */
if(ev.id == SYS_USB_CONNECTED)
{
@@ -265,46 +348,148 @@ static void rmi_thread(void)
int absolute_y = u.s.absolute.y_msb << 8 | u.s.absolute.y_lsb;
int nr_fingers = u.s.absolute.misc & 7;
- /* Handle the single vs two fingers event considering the following issues:
- - When they are two fingers on the touchpad the signal often
- switch between 1 and 2 fingers. We use the bool
- two_fingers_mode to "lock" the two fingers's signal
- as long as the user doesn't release the touchpad
- - User can hit the device at first with only one finger while
- trying to do a double fingers's touch. In order to "smooth"
- the signal, we set a delay on single finger so that user as
- time to actually touch with 2 finger if he meant to.
- */
-
- switch(nr_fingers)
+ switch(touchpad_mode)
{
- case 2:
- /* enter two fingers mode */
- two_fingers_mode = 1;
- touchpad_btns = BUTTON_TWO_FINGERS;
+ case MODE_TWO_FINGERS:
+ /* go back to idle mode when there is no finger */
+ if(nr_fingers == 0)
+ touchpad_mode = MODE_IDLE;
+ touchpad_btns = (nr_fingers == 2) ? BUTTON_TWO_FINGERS : 0;
break;
- case 1:
- /* Ignore any touch when in two fingers mode */
- if (two_fingers_mode)
- touchpad_btns = BUTTON_TWO_FINGERS;
- else
+ case MODE_IDLE:
+ touchpad_btns = 0;
+ /* consider the number of fingers to determine next transition */
+ if(nr_fingers == 1)
+ {
+ button_detected = find_button(absolute_x, absolute_y);
+ button_first_tick = current_tick;
+ touchpad_mode = MODE_BUTTON_WAIT;
+ button_arrow = is_button_arrow(button_detected);
+ get_arrow_pos(absolute_x, absolute_y, &button_arrow_x, &button_arrow_y);
+ /* make sure we'll "think" again at the end of the delay */
+ next_action_tick = current_tick + TOUCHPAD_BUTTON_DELAY;
+ }
+ else if(nr_fingers == 2)
+ {
+ touchpad_mode = MODE_TWO_FINGERS;
+ }
+ break;
+ case MODE_BUTTON_WAIT:
+ {
+ /* check number of fingers */
+ if(nr_fingers == 1)
{
- if(button_delay > 2)
- touchpad_btns = find_button(absolute_x, absolute_y);
+ /* check position */
+ int new_button = find_button(absolute_x, absolute_y);
+ bool new_ar = is_button_arrow(new_button);
+ int new_ar_x, new_ar_y;
+ get_arrow_pos(absolute_x, absolute_y, &new_ar_x, &new_ar_y);
+ int dist = MAX(ABS(button_arrow_x - new_ar_x), ABS(button_arrow_y - new_ar_y));
+ /* same position as before ? */
+ if(new_button != button_detected)
+ {
+ button_detected = new_button;
+ button_arrow = new_ar;
+ button_arrow_x = new_ar_x;
+ button_arrow_y = new_ar_y;
+ button_first_tick = current_tick;
+ }
else
- button_delay++;
+ {
+ /* sliding ? */
+ if(button_arrow && new_ar && dist >= TOUCHPAD_SLIDING_THRESH)
+ {
+ touchpad_mode = MODE_SLIDING;
+ sliding_button = find_sliding(button_arrow_x, button_arrow_y,
+ new_ar_x, new_ar_y);
+ touchpad_btns = sliding_button;
+ button_arrow_x = new_ar_x;
+ button_arrow_y = new_ar_y;
+ /* setup button in case sliding stops */
+ button_detected = new_button;
+ button_first_tick = current_tick;
+ next_action_tick = current_tick + TOUCHPAD_BUTTON_DELAY;
+ }
+ /* time elapsed ? */
+ if(current_tick - button_first_tick >= TOUCHPAD_BUTTON_DELAY)
+ touchpad_btns = button_detected;
+ else
+ touchpad_btns = 0;
+ }
+ }
+ else if(nr_fingers == 2)
+ {
+ touchpad_btns = 0;
+ touchpad_mode = MODE_TWO_FINGERS;
+ }
+ else
+ {
+ /* if it was on a button, it's a "tap", ie a short press */
+ /* FIXME: generate tap, doesn't work, should check for timing */
+ queue_post(&button_queue,button_detected,0);
+ touchpad_btns = 0;
+ touchpad_mode = MODE_IDLE;
}
break;
- case 0:
- /* reset two fingers mode and delay */
- two_fingers_mode = 0;
- button_delay = 0;
+ }
+ case MODE_SLIDING:
touchpad_btns = 0;
+ if(nr_fingers == 1)
+ {
+ /* check position */
+ int new_button = find_button(absolute_x, absolute_y);
+ bool new_ar = is_button_arrow(new_button);
+ int new_ar_x, new_ar_y;
+ get_arrow_pos(absolute_x, absolute_y, &new_ar_x, &new_ar_y);
+ /* not in the arrows or time elapsed ? no more sliding ! */
+ if(!new_ar)
+ {
+ button_detected = new_button;
+ button_arrow = new_ar;
+ button_arrow_x = new_ar_x;
+ button_arrow_y = new_ar_y;
+ button_first_tick = current_tick;
+ touchpad_mode = MODE_BUTTON_WAIT;
+ next_action_tick = current_tick + TOUCHPAD_BUTTON_DELAY;
+ }
+ /* time elapsed ? */
+ else if(current_tick - button_first_tick >= TOUCHPAD_BUTTON_DELAY)
+ {
+ button_detected = new_button;
+ button_arrow = new_ar;
+ button_arrow_x = new_ar_x;
+ button_arrow_y = new_ar_y;
+ touchpad_mode = MODE_BUTTON_WAIT;
+ next_action_tick = current_tick + TOUCHPAD_IDLE_DELAY;
+ touchpad_btns = button_detected;
+ }
+ /* still sliding */
+ else
+ {
+ /* Allow change of direction (UP/DOWN or LEFT/RIGHT)
+ * or idle for a short time */
+ int new_sliding = update_sliding(button_arrow_x, button_arrow_y,
+ new_ar_x, new_ar_y, sliding_button);
+ if(new_sliding != 0)
+ {
+ button_arrow_x = new_ar_x;
+ button_arrow_y = new_ar_y;
+ sliding_button = new_sliding;
+ button_first_tick = current_tick;
+ /* FIXME: generate buton repeat */
+ }
+ }
+ }
+ else
+ {
+ touchpad_btns = 0;
+ touchpad_mode = (nr_fingers == 2) ? MODE_TWO_FINGERS : MODE_IDLE;
+ }
break;
default:
- break;
- }
-
+ touchpad_mode = MODE_IDLE;
+ }
+
/* enable interrupt */
imx233_setup_pin_irq(0, 27, true, true, false, &rmi_attn_cb);
}
diff --git a/firmware/usbstack/usb_hid.c b/firmware/usbstack/usb_hid.c
index 213f971..c6a9440 100644
--- a/firmware/usbstack/usb_hid.c
+++ b/firmware/usbstack/usb_hid.c
@@ -109,6 +109,9 @@ typedef enum
#ifdef HAVE_USB_HID_MOUSE
REPORT_ID_MOUSE,
#endif
+#ifdef HAVE_USB_HID_TOUCHPAD
+ REPORT_ID_TOUCHPAD,
+#endif
REPORT_ID_COUNT,
} report_id_t;
@@ -552,6 +555,13 @@ static size_t descriptor_report_get(unsigned char *dest)
PACK_VAL(report, END_COLLECTION);
#endif /* HAVE_USB_HID_MOUSE */
+#ifdef HAVE_USB_HID_TOUCHPAD
+ pack_parameter(&report, 0, 1, USAGE_PAGE, HID_USAGE_PAGE_DIGITIZER);
+ pack_parameter(&report, 0, 1, CONSUMER_USAGE, HID_CONSUMER_USAGE_DIGITIZER_TOUCHPAD);
+ pack_parameter(&report, 0, 1, COLLECTION, COLLECTION_APPLICATION);
+ PACK_VAL(report, END_COLLECTION);
+#endif
+
return (size_t)(report - dest);
}
diff --git a/firmware/usbstack/usb_hid_usage_tables.h b/firmware/usbstack/usb_hid_usage_tables.h
index d23c704..9adf50e 100644
--- a/firmware/usbstack/usb_hid_usage_tables.h
+++ b/firmware/usbstack/usb_hid_usage_tables.h
@@ -700,6 +700,7 @@ typedef enum usage_page
#define HID_CONSUMER_USAGE_AC_SPLIT 0x29A
#define HID_CONSUMER_USAGE_AC_DISRIBUTE_HORIZONTALLY 0x29B
#define HID_CONSUMER_USAGE_AC_DISTRIBUTE_VERTICALLY 0x29C
+#define HID_CONSUMER_USAGE_DIGITIZER_TOUCHPAD 0x5
#ifdef HAVE_USB_HID_MOUSE
/* Mouse defines (custom made - Rockbox specific) */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment