FS#7738 - Scroll wheel acceleration for iPod

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15681 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Linus Nielsen Feltzing 2007-11-19 11:05:54 +00:00
parent a8c020288c
commit e75327b332
11 changed files with 231 additions and 67 deletions

View file

@ -1011,8 +1011,7 @@ bool gui_synclist_do_button(struct gui_synclist * lists,
int i; int i;
#ifdef HAVE_SCROLLWHEEL #ifdef HAVE_SCROLLWHEEL
int next_item_modifier = button_apply_acceleration(get_action_data(), int next_item_modifier = button_apply_acceleration(get_action_data());
WHEEL_ACCELERATION_FACTOR);
#else #else
static int next_item_modifier = 1; static int next_item_modifier = 1;
static int last_accel_tick = 0; static int last_accel_tick = 0;

View file

@ -522,26 +522,40 @@ void button_clear_queue(void)
#endif /* SIMULATOR */ #endif /* SIMULATOR */
#ifdef HAVE_SCROLLWHEEL #ifdef HAVE_SCROLLWHEEL
/* WHEEL_ACCEL_FACTOR = 2^16 / WHEEL_ACCEL_START */
#define WHEEL_ACCEL_FACTOR (1<<16)/WHEEL_ACCEL_START
/** /**
* data: * data:
* [31] Use acceleration * [31] Use acceleration
* [30:24] Message post count (skipped + 1) (1-127) * [30:24] Message post count (skipped + 1) (1-127)
* [23:0] Velocity - clicks/uS - 0.24 fixed point * [23:0] Velocity - degree/sec
* *
* factor: * WHEEL_ACCEL_FACTOR:
* Wheel acceleration scaling factor - x.24 fixed point - * Value in degree/sec -- configurable via settings -- above which
* no greater than what will not overflow 64 bits when multiplied * the accelerated scrolling starts. Factor is internally scaled by
* by the driver's maximum velocity in (clicks/usec)^2 in 0.24 * 1<<16 in respect to the following 32bit integer operations.
*/ */
int button_apply_acceleration(unsigned int data, unsigned int factor) int button_apply_acceleration(const unsigned int data)
{ {
int delta = (data >> 24) & 0x7f; int delta = (data >> 24) & 0x7f;
if ((data & (1 << 31)) != 0) if ((data & (1 << 31)) != 0)
{ {
/* read driver's velocity from data */
unsigned int v = data & 0xffffff; unsigned int v = data & 0xffffff;
v = factor * (unsigned long long)v*v / 0xffffffffffffull; /* v = 28.4 fixed point */
v = (WHEEL_ACCEL_FACTOR * v)>>(16-4);
/* Calculate real numbers item to scroll based upon acceleration
* setting, use correct roundoff */
#if (WHEEL_ACCELERATION == 1)
v = (v*v + (1<< 7))>> 8;
#elif (WHEEL_ACCELERATION == 2)
v = (v*v*v + (1<<11))>>12;
#elif (WHEEL_ACCELERATION == 3)
v = (v*v*v*v + (1<<15))>>16;
#endif
if (v > 1) if (v > 1)
delta *= v; delta *= v;

View file

@ -52,7 +52,7 @@ void wheel_send_events(bool send);
#endif #endif
#ifdef HAVE_SCROLLWHEEL #ifdef HAVE_SCROLLWHEEL
int button_apply_acceleration(unsigned int data, unsigned int factor); int button_apply_acceleration(const unsigned int data);
#endif #endif
#define BUTTON_NONE 0x00000000 #define BUTTON_NONE 0x00000000

View file

@ -100,9 +100,10 @@
/* define this if the unit uses a scrollwheel for navigation */ /* define this if the unit uses a scrollwheel for navigation */
#define HAVE_SCROLLWHEEL #define HAVE_SCROLLWHEEL
/* define wheel acceleration scaling factor */ /* define from which rotation speed [degree/sec] on the acceleration starts */
/* Range for this target: 0xffffff*(0.0-16.000000894069724921567733381255) */ #define WHEEL_ACCEL_START 540
#define WHEEL_ACCELERATION_FACTOR (0xffffff*7) /* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */
#define WHEEL_ACCELERATION 1
/* define this if you have a flash memory storage */ /* define this if you have a flash memory storage */
#define HAVE_FLASH_STORAGE #define HAVE_FLASH_STORAGE

View file

@ -86,6 +86,13 @@
/* Define this for LCD backlight available */ /* Define this for LCD backlight available */
#define HAVE_BACKLIGHT #define HAVE_BACKLIGHT
/* define this if the unit uses a scrollwheel for navigation */
#define HAVE_SCROLLWHEEL
/* define from which rotation speed [degree/sec] on the acceleration starts */
#define WHEEL_ACCEL_START 270
/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */
#define WHEEL_ACCELERATION 3
/* Define this if you can detect headphones */ /* Define this if you can detect headphones */
#define HAVE_HEADPHONE_DETECTION #define HAVE_HEADPHONE_DETECTION

View file

@ -73,6 +73,13 @@
/* Define this for LCD backlight available */ /* Define this for LCD backlight available */
#define HAVE_BACKLIGHT #define HAVE_BACKLIGHT
/* define this if the unit uses a scrollwheel for navigation */
#define HAVE_SCROLLWHEEL
/* define from which rotation speed [degree/sec] on the acceleration starts */
#define WHEEL_ACCEL_START 270
/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */
#define WHEEL_ACCELERATION 3
/* Define this if you can detect headphones */ /* Define this if you can detect headphones */
#define HAVE_HEADPHONE_DETECTION #define HAVE_HEADPHONE_DETECTION

View file

@ -83,6 +83,13 @@
/* We can fade the backlight by using PWM */ /* We can fade the backlight by using PWM */
#define HAVE_BACKLIGHT_PWM_FADING #define HAVE_BACKLIGHT_PWM_FADING
/* define this if the unit uses a scrollwheel for navigation */
#define HAVE_SCROLLWHEEL
/* define from which rotation speed [degree/sec] on the acceleration starts */
#define WHEEL_ACCEL_START 270
/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */
#define WHEEL_ACCELERATION 3
/* Define this if you can detect headphones */ /* Define this if you can detect headphones */
#define HAVE_HEADPHONE_DETECTION #define HAVE_HEADPHONE_DETECTION

View file

@ -82,6 +82,13 @@
/* We can fade the backlight by using PWM */ /* We can fade the backlight by using PWM */
#define HAVE_BACKLIGHT_PWM_FADING #define HAVE_BACKLIGHT_PWM_FADING
/* define this if the unit uses a scrollwheel for navigation */
#define HAVE_SCROLLWHEEL
/* define from which rotation speed [degree/sec] on the acceleration starts */
#define WHEEL_ACCEL_START 270
/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */
#define WHEEL_ACCELERATION 3
/* Define this if you can detect headphones */ /* Define this if you can detect headphones */
#define HAVE_HEADPHONE_DETECTION #define HAVE_HEADPHONE_DETECTION

View file

@ -82,6 +82,13 @@
/* We can fade the backlight by using PWM */ /* We can fade the backlight by using PWM */
#define HAVE_BACKLIGHT_PWM_FADING #define HAVE_BACKLIGHT_PWM_FADING
/* define this if the unit uses a scrollwheel for navigation */
#define HAVE_SCROLLWHEEL
/* define from which rotation speed [degree/sec] on the acceleration starts */
#define WHEEL_ACCEL_START 270
/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */
#define WHEEL_ACCELERATION 3
/* Define this if you can detect headphones */ /* Define this if you can detect headphones */
#define HAVE_HEADPHONE_DETECTION #define HAVE_HEADPHONE_DETECTION

View file

@ -40,6 +40,30 @@
#include "system.h" #include "system.h"
#include "powermgmt.h" #include "powermgmt.h"
#define WHEEL_FAST_OFF_TIMEOUT 250000 /* timeout for acceleration = 250ms */
#define WHEEL_REPEAT_TIMEOUT 250000 /* timeout for button repeat = 250ms */
#define WHEEL_UNTOUCH_TIMEOUT 150000 /* timeout for untouching wheel = 100ms */
#define WHEELCLICKS_PER_ROTATION 96 /* wheelclicks per full rotation */
/* This amount of clicks is needed for at least scrolling 1 item. Choose small values
* to have high sensitivity but few precision, choose large values to have less
* sensitivity and good precision. */
#if defined(IPOD_NANO)
#define WHEEL_SENSITIVITY 6 /* iPod nano has smaller wheel, lower sensitivity needed */
#else
#define WHEEL_SENSITIVITY 4 /* default sensitivity */
#endif
int old_wheel_value = -1;
int new_wheel_value = 0;
int repeat = 0;
int wheel_delta = 0;
bool wheel_is_touched = false;
unsigned int accumulated_wheel_delta = 0;
unsigned int wheel_repeat = 0;
unsigned int wheel_velocity = 0;
unsigned long last_wheel_usec = 0;
/* Variable to use for setting button status in interrupt handler */ /* Variable to use for setting button status in interrupt handler */
int int_btn = BUTTON_NONE; int int_btn = BUTTON_NONE;
#ifdef HAVE_WHEEL_POSITION #ifdef HAVE_WHEEL_POSITION
@ -89,16 +113,15 @@ static inline int ipod_4g_button_read(void)
int btn = BUTTON_NONE; int btn = BUTTON_NONE;
unsigned reg = 0x7000c104; unsigned reg = 0x7000c104;
if ((inl(0x7000c104) & 0x4000000) != 0) { if ((inl(0x7000c104) & 0x4000000) != 0)
{
unsigned status = inl(0x7000c140); unsigned status = inl(0x7000c140);
reg = reg + 0x3C; /* 0x7000c140 */ reg = reg + 0x3C; /* 0x7000c140 */
outl(0x0, 0x7000c140); /* clear interrupt status? */ outl(0x0, 0x7000c140); /* clear interrupt status? */
if ((status & 0x800000ff) == 0x8000001a) { if ((status & 0x800000ff) == 0x8000001a)
static int old_wheel_value IDATA_ATTR = -1; {
static int wheel_repeat = 0;
if (status & 0x100) if (status & 0x100)
btn |= BUTTON_SELECT; btn |= BUTTON_SELECT;
if (status & 0x200) if (status & 0x200)
@ -109,63 +132,152 @@ static inline int ipod_4g_button_read(void)
btn |= BUTTON_PLAY; btn |= BUTTON_PLAY;
if (status & 0x1000) if (status & 0x1000)
btn |= BUTTON_MENU; btn |= BUTTON_MENU;
if (status & 0x40000000) { if (status & 0x40000000)
/* NB: highest wheel = 0x5F, clockwise increases */ {
int new_wheel_value = (status << 9) >> 25; unsigned long usec = USEC_TIMER;
/* Highest wheel = 0x5F, clockwise increases */
new_wheel_value = (status >> 16) & 0x7f;
whl = new_wheel_value; whl = new_wheel_value;
/* switch on backlight (again), reset power-off timer */
backlight_on(); backlight_on();
reset_poweroff_timer(); reset_poweroff_timer();
/* The queue should have no other events when scrolling */
if (queue_empty(&button_queue) && old_wheel_value >= 0) { /* Check whether the scrollwheel was untouched by accident or by will. */
/* This is needed because wheel may be untoched very shortly during rotation */
/* This is for later = BUTTON_SCROLL_TOUCH;*/ if ( (!wheel_is_touched) && TIME_AFTER(usec, last_wheel_usec + WHEEL_UNTOUCH_TIMEOUT) )
int wheel_delta = new_wheel_value - old_wheel_value; {
unsigned long data; /* wheel has been really untouched -> reset internal variables */
int wheel_keycode; old_wheel_value = -1;
wheel_velocity = 0;
if (wheel_delta < -48) accumulated_wheel_delta = 0;
wheel_delta += 96; /* Forward wrapping case */ wheel_repeat = BUTTON_NONE;
else if (wheel_delta > 48) }
wheel_delta -= 96; /* Backward wrapping case */ else
{
if (wheel_delta > 4) { /* wheel was shortly untouched by accident -> leave internal variables */
wheel_keycode = BUTTON_SCROLL_FWD; wheel_is_touched = true;
} else if (wheel_delta < -4) {
wheel_keycode = BUTTON_SCROLL_BACK;
} else goto wheel_end;
#ifdef HAVE_WHEEL_POSITION
if (send_events)
#endif
{
data = (wheel_delta << 16) | new_wheel_value;
queue_post(&button_queue, wheel_keycode | wheel_repeat,
data);
}
if (!wheel_repeat) wheel_repeat = BUTTON_REPEAT;
} }
old_wheel_value = new_wheel_value; if (old_wheel_value >= 0)
} else if (old_wheel_value >= 0) { {
/* scroll wheel up */ /* This is for later = BUTTON_SCROLL_TOUCH;*/
old_wheel_value = -1; wheel_delta = new_wheel_value - old_wheel_value;
wheel_repeat = 0; unsigned int wheel_keycode = BUTTON_NONE;
/* Taking into account wrapping during transition from highest
* to lowest wheel position and back */
if (wheel_delta < -WHEELCLICKS_PER_ROTATION/2)
wheel_delta += WHEELCLICKS_PER_ROTATION; /* Forward wrapping case */
else if (wheel_delta > WHEELCLICKS_PER_ROTATION/2)
wheel_delta -= WHEELCLICKS_PER_ROTATION; /* Backward wrapping case */
/* Getting direction and wheel_keycode from wheel_delta.
* Need at least some clicks to be sure to avoid haptic fuzziness */
if (wheel_delta >= WHEEL_SENSITIVITY)
wheel_keycode = BUTTON_SCROLL_FWD;
else if (wheel_delta <= -WHEEL_SENSITIVITY)
wheel_keycode = BUTTON_SCROLL_BACK;
else
wheel_keycode = BUTTON_NONE;
if (wheel_keycode != BUTTON_NONE)
{
long v = (usec - last_wheel_usec) & 0x7fffffff;
/* undo signedness */
wheel_delta = (wheel_delta>0) ? wheel_delta : -wheel_delta;
/* add the current wheel_delta */
accumulated_wheel_delta += wheel_delta;
v = v ? (1000000 * wheel_delta) / v : 0; /* clicks/sec = 1000000 * clicks/usec */
v = (v * 360) / WHEELCLICKS_PER_ROTATION; /* conversion to degree/sec */
v = (v<0) ? -v : v; /* undo signedness */
/* some velocity filtering to smooth things out */
wheel_velocity = (31 * wheel_velocity + v) / 32;
/* limit to 24 bit */
wheel_velocity = (wheel_velocity>0xffffff) ? 0xffffff : wheel_velocity;
/* assume REPEAT = off */
repeat = 0;
/* direction reversals must nullify acceleration and accumulator */
if (wheel_keycode != wheel_repeat)
{
wheel_repeat = wheel_keycode;
wheel_velocity = 0;
accumulated_wheel_delta = 0;
}
/* on same direction REPEAT is assumed when new click is within timeout */
else if (TIME_BEFORE(usec, last_wheel_usec + WHEEL_REPEAT_TIMEOUT))
{
repeat = BUTTON_REPEAT;
}
/* timeout nullifies acceleration and accumulator */
if (TIME_AFTER(usec, last_wheel_usec + WHEEL_FAST_OFF_TIMEOUT))
{
wheel_velocity = 0;
accumulated_wheel_delta = 0;
}
#ifdef HAVE_WHEEL_POSITION
if (send_events)
#endif
/* The queue should have no other events when scrolling */
if (queue_empty(&button_queue))
{
/* each WHEEL_SENSITIVITY clicks = scrolling 1 item */
accumulated_wheel_delta /= WHEEL_SENSITIVITY;
#ifdef HAVE_SCROLLWHEEL
/* use data-format for HAVE_SCROLLWHEEL */
/* always use acceleration mode (1<<31) */
/* always set message post count to (1<<24) for iPod */
/* this way the scrolling is always calculated from wheel_velocity */
queue_post(&button_queue, wheel_keycode | repeat,
(1<<31) | (1 << 24) | wheel_velocity);
#else
queue_post(&button_queue, wheel_keycode | repeat,
(accumulated_wheel_delta << 16) | new_wheel_value);
#endif
accumulated_wheel_delta = 0;
}
last_wheel_usec = usec;
old_wheel_value = new_wheel_value;
}
}
else
{
/* scrollwheel was touched for the first time after finger lifting */
old_wheel_value = new_wheel_value;
wheel_is_touched = true;
}
}
else
{
/* In this case the finger was lifted from the scrollwheel. */
wheel_is_touched = false;
} }
} else if (status == 0xffffffff) { }
else if (status == 0xffffffff)
{
opto_i2c_init(); opto_i2c_init();
} }
} }
wheel_end: if ((inl(reg) & 0x8000000) != 0)
{
if ((inl(reg) & 0x8000000) != 0) {
outl(0xffffffff, 0x7000c120); outl(0xffffffff, 0x7000c120);
outl(0xffffffff, 0x7000c124); outl(0xffffffff, 0x7000c124);
} }
/* Save the new absolute wheel position */ /* Save the new absolute wheel position */
#ifdef HAVE_WHEEL_POSITION
wheel_position = whl; wheel_position = whl;
#endif
return btn; return btn;
} }
@ -200,14 +312,18 @@ void ipod_4g_button_int(void)
void button_init_device(void) void button_init_device(void)
{ {
opto_i2c_init(); opto_i2c_init();
/* hold button - enable as input */ /* hold button - enable as input */
GPIOA_ENABLE |= 0x20; GPIOA_ENABLE |= 0x20;
GPIOA_OUTPUT_EN &= ~0x20; GPIOA_OUTPUT_EN &= ~0x20;
/* hold button - set interrupt levels */ /* hold button - set interrupt levels */
GPIOA_INT_LEV = ~(GPIOA_INPUT_VAL & 0x20); GPIOA_INT_LEV = ~(GPIOA_INPUT_VAL & 0x20);
GPIOA_INT_CLR = GPIOA_INT_STAT & 0x20; GPIOA_INT_CLR = GPIOA_INT_STAT & 0x20;
/* enable interrupts */ /* enable interrupts */
GPIOA_INT_EN = 0x20; GPIOA_INT_EN = 0x20;
/* unmask interrupt */ /* unmask interrupt */
CPU_INT_EN = 0x40000000; CPU_INT_EN = 0x40000000;
CPU_HI_INT_EN = I2C_MASK; CPU_HI_INT_EN = I2C_MASK;

View file

@ -27,6 +27,7 @@
#define WHEEL_REPEAT_INTERVAL 300000 #define WHEEL_REPEAT_INTERVAL 300000
#define WHEEL_FAST_ON_INTERVAL 20000 #define WHEEL_FAST_ON_INTERVAL 20000
#define WHEEL_FAST_OFF_INTERVAL 60000 #define WHEEL_FAST_OFF_INTERVAL 60000
#define WHEELCLICKS_PER_ROTATION 48 /* wheelclicks per full rotation */
/* Clickwheel */ /* Clickwheel */
#ifndef BOOTLOADER #ifndef BOOTLOADER
@ -137,10 +138,8 @@ void clickwheel_int(void)
unsigned long usec = USEC_TIMER; unsigned long usec = USEC_TIMER;
unsigned v = (usec - last_wheel_usec) & 0x7fffffff; unsigned v = (usec - last_wheel_usec) & 0x7fffffff;
/* wheel velocity in 0.24 fixed point - clicks/uS */ v = (v>0) ? 1000000 / v : 0; /* clicks/sec = 1000000 * clicks/usec */
v = (v>0xffffff) ? 0xffffff : v; /* limit to 24 bit */
/* velocity cap to 18 bits to allow up to x16 scaling */
v = (v < 0x40) ? 0xffffff / 0x40 : 0xffffff / v;
/* some velocity filtering to smooth things out */ /* some velocity filtering to smooth things out */
wheel_velocity = (7*wheel_velocity + v) / 8; wheel_velocity = (7*wheel_velocity + v) / 8;
@ -173,12 +172,12 @@ void clickwheel_int(void)
else else
{ {
/* fast ON gets filtered to avoid inadvertent jumps to fast mode */ /* fast ON gets filtered to avoid inadvertent jumps to fast mode */
if (repeat && wheel_velocity > 0xffffff/WHEEL_FAST_ON_INTERVAL) if (repeat && wheel_velocity > 1000000/WHEEL_FAST_ON_INTERVAL)
{ {
/* moving into fast mode */ /* moving into fast mode */
wheel_fast_mode = 1 << 31; wheel_fast_mode = 1 << 31;
wheel_click_count = 0; wheel_click_count = 0;
wheel_velocity = 0xffffff/WHEEL_FAST_OFF_INTERVAL; wheel_velocity = 1000000/WHEEL_FAST_OFF_INTERVAL;
} }
else if (++wheel_click_count < 2) else if (++wheel_click_count < 2)
{ {
@ -190,7 +189,7 @@ void clickwheel_int(void)
} }
if (TIME_AFTER(current_tick, next_backlight_on) || if (TIME_AFTER(current_tick, next_backlight_on) ||
v <= 0xffffff/(1000000/4)) v <= 4)
{ {
/* poke backlight to turn it on or maintain it no more often /* poke backlight to turn it on or maintain it no more often
than every 1/4 second*/ than every 1/4 second*/
@ -214,7 +213,7 @@ void clickwheel_int(void)
if (queue_empty(&button_queue)) if (queue_empty(&button_queue))
{ {
queue_post(&button_queue, btn, wheel_fast_mode | queue_post(&button_queue, btn, wheel_fast_mode |
(wheel_delta << 24) | wheel_velocity); (wheel_delta << 24) | wheel_velocity*360/WHEELCLICKS_PER_ROTATION);
/* message posted - reset delta */ /* message posted - reset delta */
wheel_delta = 1; wheel_delta = 1;
} }