iPod 3G: Implement wheel acceleration and repeats. Chosen settings feel pretty decent...
[kugel-rb.git] / firmware / target / arm / ipod / button-1g-3g.c
blob044e887453413cad8c776e6b9f38c0ddf2d26e5b
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 by Daniel Stenberg
12 * iPod driver based on code from the ipodlinux project - http://ipodlinux.org
13 * Adapted for Rockbox in December 2005
14 * Original file: linux/arch/armnommu/mach-ipod/keyboard.c
15 * Copyright (c) 2003-2005 Bernard Leach (leachbj@bouncycastle.org)
18 * This program is free software; you can redistribute it and/or
19 * modify it under the terms of the GNU General Public License
20 * as published by the Free Software Foundation; either version 2
21 * of the License, or (at your option) any later version.
23 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
24 * KIND, either express or implied.
26 ****************************************************************************/
29 * Rockbox button functions
32 #include <stdlib.h>
33 #include "config.h"
34 #include "cpu.h"
35 #include "system.h"
36 #include "button.h"
37 #include "kernel.h"
38 #include "backlight.h"
39 #include "serial.h"
40 #include "power.h"
41 #include "powermgmt.h"
42 #include "hwcompat.h"
44 static int int_btn = BUTTON_NONE;
45 #ifdef IPOD_1G2G
46 /* The 1st Gen wheel draws ~12mA when enabled permanently. Therefore
47 * we only enable it for a very short time to check for changes every
48 * tick, and only keep it enabled if there is activity. */
49 #define WHEEL_TIMEOUT (HZ/4)
50 #endif
52 #ifdef IPOD_3G
53 #define WHEELCLICKS_PER_ROTATION 96
54 #define WHEEL_BASE_SENSITIVITY 6 /* Compute every ... clicks */
55 #define WHEEL_REPEAT_VELOCITY 45 /* deg/s */
56 #define WHEEL_SMOOTHING_VELOCITY 100 /* deg/s */
58 static void handle_scroll_wheel(int new_scroll, int was_hold)
60 static const signed char scroll_state[4][4] = {
61 {0, 1, -1, 0},
62 {-1, 0, 0, 1},
63 {1, 0, 0, -1},
64 {0, -1, 1, 0}
67 static int prev_scroll = 0;
68 static int direction = 0;
69 static int count = 0;
70 static long next_backlight_on = 0;
72 static unsigned long last_wheel_usec = 0;
73 static unsigned long wheel_delta = 1ul << 24;
74 static unsigned long wheel_velocity = 0;
76 int wheel_keycode = BUTTON_NONE;
77 int scroll = scroll_state[prev_scroll][new_scroll];
78 unsigned long usec;
79 unsigned long v;
81 prev_scroll = new_scroll;
83 if (direction != scroll) {
84 /* direction reversal - reset all */
85 direction = scroll;
86 wheel_velocity = 0;
87 wheel_delta = 1ul << 24;
88 count = 0;
91 if (was_hold) {
92 /* hold - reset all */
93 wheel_velocity = 0;
94 wheel_delta = 1ul << 24;
95 count = 0;
96 return;
99 /* poke backlight every 1/4s of activity */
100 if (TIME_AFTER(current_tick, next_backlight_on)) {
101 backlight_on();
102 reset_poweroff_timer();
103 next_backlight_on = current_tick + HZ/4;
106 /* has wheel travelled far enough? */
107 if (++count < WHEEL_BASE_SENSITIVITY) {
108 return;
111 /* reset travel count and do calculations */
112 count = 0;
114 /* 1st..3rd Gen wheel has inverse direction mapping
115 * compared to Mini 1st Gen wheel. */
116 switch (direction) {
117 case 1:
118 wheel_keycode = BUTTON_SCROLL_BACK;
119 break;
120 case -1:
121 wheel_keycode = BUTTON_SCROLL_FWD;
122 break;
123 default:
124 /* only happens if we get out of sync */
125 return;
128 /* have a keycode */
130 usec = USEC_TIMER;
131 v = usec - last_wheel_usec;
133 /* calculate deg/s based upon sensitivity-adjusted interrupt period */
135 if ((long)v <= 0) {
136 /* timer wrapped (no activity for awhile), skip acceleration */
137 v = 0;
138 wheel_delta = 1ul << 24;
140 else {
141 if (v > 0xfffffffful/WHEELCLICKS_PER_ROTATION) {
142 v = 0xfffffffful/WHEELCLICKS_PER_ROTATION; /* check overflow below */
145 v = 360000000ul*WHEEL_BASE_SENSITIVITY / (v*WHEELCLICKS_PER_ROTATION);
147 if (v > 0xfffffful)
148 v = 0xfffffful; /* limit to 24 bits */
151 if (v < WHEEL_SMOOTHING_VELOCITY) {
152 /* very slow - no smoothing */
153 wheel_velocity = v;
155 else {
156 /* some velocity filtering to smooth things out */
157 wheel_velocity = (7*wheel_velocity + v) / 8;
160 if (v >= WHEEL_REPEAT_VELOCITY) {
161 /* quick enough - generate repeats - use unsmoothed v to guage */
162 wheel_keycode |= BUTTON_REPEAT;
165 if (queue_empty(&button_queue)) {
166 /* post wheel keycode with wheel data */
167 queue_post(&button_queue, wheel_keycode,
168 (wheel_velocity >= WHEEL_ACCEL_START ? (1ul << 31) : 0)
169 | wheel_delta | wheel_velocity);
170 /* message posted - reset delta */
171 wheel_delta = 1ul << 24;
173 else {
174 /* skipped post - increment delta and limit to 7 bits */
175 wheel_delta += 1ul << 24;
177 if (wheel_delta > (0x7ful << 24))
178 wheel_delta = 0x7ful << 24;
181 last_wheel_usec = usec;
183 #else
184 static void handle_scroll_wheel(int new_scroll, int was_hold)
186 int wheel_keycode = BUTTON_NONE;
187 static int prev_scroll = -1;
188 static int direction = 0;
189 static int count = 0;
190 static int scroll_state[4][4] = {
191 {0, 1, -1, 0},
192 {-1, 0, 0, 1},
193 {1, 0, 0, -1},
194 {0, -1, 1, 0}
197 if ( prev_scroll == -1 ) {
198 prev_scroll = new_scroll;
200 else if (direction != scroll_state[prev_scroll][new_scroll]) {
201 direction = scroll_state[prev_scroll][new_scroll];
202 count = 0;
204 else if (!was_hold) {
205 backlight_on();
206 reset_poweroff_timer();
207 if (++count == 6) { /* reduce sensitivity */
208 count = 0;
209 /* 1st..3rd Gen wheel has inverse direction mapping
210 * compared to Mini 1st Gen wheel. */
211 switch (direction) {
212 case 1:
213 wheel_keycode = BUTTON_SCROLL_BACK;
214 break;
215 case -1:
216 wheel_keycode = BUTTON_SCROLL_FWD;
217 break;
218 default:
219 /* only happens if we get out of sync */
220 break;
224 if (wheel_keycode != BUTTON_NONE && queue_empty(&button_queue))
225 queue_post(&button_queue, wheel_keycode, 0);
226 prev_scroll = new_scroll;
228 #endif /* IPOD_3G */
230 static int ipod_3g_button_read(void)
232 unsigned char source, state;
233 static bool was_hold = false;
234 int btn = BUTTON_NONE;
236 /* get source of interupts */
237 source = GPIOA_INT_STAT;
239 /* get current keypad status */
240 state = GPIOA_INPUT_VAL;
242 /* toggle interrupt level */
243 GPIOA_INT_LEV = ~state;
245 #ifdef IPOD_3G
246 if (was_hold && source == 0x40 && state == 0xbf) {
247 /* ack any active interrupts */
248 GPIOA_INT_CLR = source;
249 return BUTTON_NONE;
251 was_hold = false;
253 if ((state & 0x20) == 0) {
254 /* 3g hold switch is active low */
255 was_hold = true;
256 /* hold switch on 3g causes all outputs to go low */
257 /* we shouldn't interpret these as key presses */
258 GPIOA_INT_CLR = source;
259 return BUTTON_NONE;
261 #elif defined IPOD_1G2G
262 if (state & 0x20) {
263 /* 1g/2g hold switch is active high */
264 GPIOA_INT_CLR = source;
265 return BUTTON_NONE;
267 #endif
268 if ((state & 0x1) == 0) {
269 btn |= BUTTON_RIGHT;
271 if ((state & 0x2) == 0) {
272 btn |= BUTTON_SELECT;
274 if ((state & 0x4) == 0) {
275 btn |= BUTTON_PLAY;
277 if ((state & 0x8) == 0) {
278 btn |= BUTTON_LEFT;
280 if ((state & 0x10) == 0) {
281 btn |= BUTTON_MENU;
284 if (source & 0xc0) {
285 handle_scroll_wheel((state & 0xc0) >> 6, was_hold);
288 /* ack any active interrupts */
289 GPIOA_INT_CLR = source;
291 return btn;
294 void ipod_3g_button_int(void)
296 CPU_INT_DIS = GPIO_MASK;
297 int_btn = ipod_3g_button_read();
298 CPU_INT_EN = GPIO_MASK;
301 void button_init_device(void)
303 GPIOA_ENABLE = 0xff;
304 GPIOA_OUTPUT_EN = 0;
306 GPIOA_INT_LEV = ~GPIOA_INPUT_VAL;
307 GPIOA_INT_CLR = GPIOA_INT_STAT;
309 #ifdef IPOD_1G2G
310 if ((IPOD_HW_REVISION >> 16) == 1)
311 { /* enable scroll wheel */
312 GPIOB_ENABLE |= 0x01;
313 GPIOB_OUTPUT_EN |= 0x01;
314 GPIOB_OUTPUT_VAL |= 0x01;
316 #endif
317 GPIOA_INT_EN = 0xff;
319 CPU_INT_EN = GPIO_MASK;
323 * Get button pressed from hardware
325 int button_read_device(void)
327 static bool hold_button = false;
328 bool hold_button_old;
329 #ifdef IPOD_1G2G
330 static int wheel_timeout = 0;
331 static unsigned char last_wheel_value = 0;
332 unsigned char wheel_value;
334 if ((IPOD_HW_REVISION >> 16) == 1)
336 if (!hold_button && (wheel_timeout == 0))
338 GPIOB_OUTPUT_VAL |= 0x01; /* enable wheel */
339 udelay(50); /* let the voltage settle */
341 wheel_value = GPIOA_INPUT_VAL >> 6;
342 if (wheel_value != last_wheel_value)
344 last_wheel_value = wheel_value;
345 wheel_timeout = WHEEL_TIMEOUT; /* keep wheel enabled */
346 GPIOA_INT_EN = 0xff; /* enable wheel interrupts */
348 if (wheel_timeout)
349 wheel_timeout--;
350 else
352 GPIOA_INT_EN = 0x3f; /* disable wheel interrupts */
353 GPIOB_OUTPUT_VAL &= ~0x01; /* disable wheel */
356 #endif
358 /* normal buttons */
359 hold_button_old = hold_button;
360 hold_button = button_hold();
362 if (hold_button != hold_button_old)
363 backlight_hold_changed(hold_button);
365 return int_btn;
368 bool button_hold(void)
370 #ifdef IPOD_1G2G
371 return (GPIOA_INPUT_VAL & 0x20);
372 #else
373 return !(GPIOA_INPUT_VAL & 0x20);
374 #endif
377 bool headphones_inserted(void)
379 #ifdef IPOD_1G2G
380 if ((IPOD_HW_REVISION >> 16) == 2)
382 /* 2G uses GPIO B bit 0 */
383 return (GPIOB_INPUT_VAL & 0x1)?true:false;
385 else
387 /* 1G has no headphone detection, so fake insertion */
388 return (true);
390 #else
391 /* 3G uses GPIO C bit 0 */
392 return (GPIOC_INPUT_VAL & 0x1)?true:false;
393 #endif