Add iPod Nano 2G µsec timer, and use it in the clickwheel driver.
[kugel-rb.git] / firmware / target / arm / ipod / button-clickwheel.c
blob9a2f38bc88e25ee60ac3585c4add6565db79f20d
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"
43 #define WHEEL_FAST_OFF_TIMEOUT 250000 /* timeout for acceleration = 250ms */
44 #define WHEEL_REPEAT_TIMEOUT 250000 /* timeout for button repeat = 250ms */
45 #define WHEEL_UNTOUCH_TIMEOUT 150000 /* timeout for untouching wheel = 150ms */
47 #ifdef CPU_PP
48 #define CLICKWHEEL_DATA (*(volatile unsigned long*)(0x7000c140))
49 #elif CONFIG_CPU==S5L8701
50 #define CLICKWHEEL00 (*(volatile unsigned long*)(0x3c200000))
51 #define CLICKWHEEL10 (*(volatile unsigned long*)(0x3c200010))
52 #define CLICKWHEELINT (*(volatile unsigned long*)(0x3c200014))
53 #define CLICKWHEEL_DATA (*(volatile unsigned long*)(0x3c200018))
54 #else
55 #error CPU architecture not supported!
56 #endif
58 #define WHEELCLICKS_PER_ROTATION 96 /* wheelclicks per full rotation */
60 /* This amount of clicks is needed for at least scrolling 1 item. Choose small values
61 * to have high sensitivity but few precision, choose large values to have less
62 * sensitivity and good precision. */
63 #if defined(IPOD_NANO) || defined(IPOD_NANO2G)
64 #define WHEEL_SENSITIVITY 6 /* iPod nano has smaller wheel, lower sensitivity needed */
65 #else
66 #define WHEEL_SENSITIVITY 4 /* default sensitivity */
67 #endif
69 int old_wheel_value = -1;
70 int new_wheel_value = 0;
71 int repeat = 0;
72 int wheel_delta = 0;
73 bool wheel_is_touched = false;
74 unsigned int accumulated_wheel_delta = 0;
75 unsigned int wheel_repeat = 0;
76 unsigned int wheel_velocity = 0;
77 unsigned long last_wheel_usec = 0;
79 /* Variable to use for setting button status in interrupt handler */
80 int int_btn = BUTTON_NONE;
81 #ifdef HAVE_WHEEL_POSITION
82 static int wheel_position = -1;
83 static bool send_events = true;
84 #endif
86 #ifdef CPU_PP
87 static void opto_i2c_init(void)
89 DEV_EN |= DEV_OPTO;
90 DEV_RS |= DEV_OPTO;
91 udelay(5);
92 DEV_RS &= ~DEV_OPTO; /* finish reset */
93 DEV_INIT1 |= INIT_BUTTONS; /* enable buttons (needed for "hold"-detection) */
95 outl(0xc00a1f00, 0x7000c100);
96 outl(0x01000000, 0x7000c104);
98 #endif
100 static inline int ipod_4g_button_read(void)
102 int whl = -1;
103 int btn = BUTTON_NONE;
105 #ifdef CPU_PP
106 if ((inl(0x7000c104) & 0x04000000) != 0)
108 #endif
109 unsigned status = CLICKWHEEL_DATA;
111 if ((status & 0x800000ff) == 0x8000001a)
113 if (status & 0x00000100)
114 btn |= BUTTON_SELECT;
115 if (status & 0x00000200)
116 btn |= BUTTON_RIGHT;
117 if (status & 0x00000400)
118 btn |= BUTTON_LEFT;
119 if (status & 0x00000800)
120 btn |= BUTTON_PLAY;
121 if (status & 0x00001000)
122 btn |= BUTTON_MENU;
123 if (status & 0x40000000)
125 unsigned long usec = USEC_TIMER;
127 /* Highest wheel = 0x5F, clockwise increases */
128 new_wheel_value = (status >> 16) & 0x7f;
129 whl = new_wheel_value;
131 /* switch on backlight (again), reset power-off timer */
132 backlight_on();
133 reset_poweroff_timer();
135 /* Check whether the scrollwheel was untouched by accident or by will. */
136 /* This is needed because wheel may be untoched very shortly during rotation */
137 if ( (!wheel_is_touched) && TIME_AFTER(usec, last_wheel_usec + WHEEL_UNTOUCH_TIMEOUT) )
139 /* wheel has been really untouched -> reset internal variables */
140 old_wheel_value = -1;
141 wheel_velocity = 0;
142 accumulated_wheel_delta = 0;
143 wheel_repeat = BUTTON_NONE;
145 else
147 /* wheel was shortly untouched by accident -> leave internal variables */
148 wheel_is_touched = true;
151 if (old_wheel_value >= 0)
153 /* This is for later = BUTTON_SCROLL_TOUCH;*/
154 wheel_delta = new_wheel_value - old_wheel_value;
155 unsigned int wheel_keycode = BUTTON_NONE;
157 /* Taking into account wrapping during transition from highest
158 * to lowest wheel position and back */
159 if (wheel_delta < -WHEELCLICKS_PER_ROTATION/2)
160 wheel_delta += WHEELCLICKS_PER_ROTATION; /* Forward wrapping case */
161 else if (wheel_delta > WHEELCLICKS_PER_ROTATION/2)
162 wheel_delta -= WHEELCLICKS_PER_ROTATION; /* Backward wrapping case */
164 /* Getting direction and wheel_keycode from wheel_delta.
165 * Need at least some clicks to be sure to avoid haptic fuzziness */
166 if (wheel_delta >= WHEEL_SENSITIVITY)
167 wheel_keycode = BUTTON_SCROLL_FWD;
168 else if (wheel_delta <= -WHEEL_SENSITIVITY)
169 wheel_keycode = BUTTON_SCROLL_BACK;
170 else
171 wheel_keycode = BUTTON_NONE;
173 if (wheel_keycode != BUTTON_NONE)
175 long v = (usec - last_wheel_usec) & 0x7fffffff;
177 /* undo signedness */
178 wheel_delta = (wheel_delta>0) ? wheel_delta : -wheel_delta;
180 /* add the current wheel_delta */
181 accumulated_wheel_delta += wheel_delta;
183 v = v ? (1000000 * wheel_delta) / v : 0; /* clicks/sec = 1000000 * clicks/usec */
184 v = (v * 360) / WHEELCLICKS_PER_ROTATION; /* conversion to degree/sec */
185 v = (v<0) ? -v : v; /* undo signedness */
187 /* some velocity filtering to smooth things out */
188 wheel_velocity = (31 * wheel_velocity + v) / 32;
189 /* limit to 24 bit */
190 wheel_velocity = (wheel_velocity>0xffffff) ? 0xffffff : wheel_velocity;
192 /* assume REPEAT = off */
193 repeat = 0;
195 /* direction reversals must nullify acceleration and accumulator */
196 if (wheel_keycode != wheel_repeat)
198 wheel_repeat = wheel_keycode;
199 wheel_velocity = 0;
200 accumulated_wheel_delta = 0;
202 /* on same direction REPEAT is assumed when new click is within timeout */
203 else if (TIME_BEFORE(usec, last_wheel_usec + WHEEL_REPEAT_TIMEOUT))
205 repeat = BUTTON_REPEAT;
207 /* timeout nullifies acceleration and accumulator */
208 if (TIME_AFTER(usec, last_wheel_usec + WHEEL_FAST_OFF_TIMEOUT))
210 wheel_velocity = 0;
211 accumulated_wheel_delta = 0;
214 #ifdef HAVE_WHEEL_POSITION
215 if (send_events)
216 #endif
217 /* The queue should have no other events when scrolling */
218 if (queue_empty(&button_queue))
220 /* each WHEEL_SENSITIVITY clicks = scrolling 1 item */
221 accumulated_wheel_delta /= WHEEL_SENSITIVITY;
222 #ifdef HAVE_SCROLLWHEEL
223 /* use data-format for HAVE_SCROLLWHEEL */
224 /* always use acceleration mode (1<<31) */
225 /* always set message post count to (1<<24) for iPod */
226 /* this way the scrolling is always calculated from wheel_velocity */
227 queue_post(&button_queue, wheel_keycode | repeat,
228 (1<<31) | (1 << 24) | wheel_velocity);
230 #else
231 queue_post(&button_queue, wheel_keycode | repeat,
232 (accumulated_wheel_delta << 16) | new_wheel_value);
233 #endif
234 accumulated_wheel_delta = 0;
236 last_wheel_usec = usec;
237 old_wheel_value = new_wheel_value;
240 else
242 /* scrollwheel was touched for the first time after finger lifting */
243 old_wheel_value = new_wheel_value;
244 wheel_is_touched = true;
247 else
249 /* In this case the finger was lifted from the scrollwheel. */
250 wheel_is_touched = false;
254 #ifdef CPU_PP
256 #endif
258 #ifdef HAVE_WHEEL_POSITION
259 /* Save the new absolute wheel position */
260 wheel_position = whl;
261 #endif
262 return btn;
265 #ifdef HAVE_WHEEL_POSITION
266 int wheel_status(void)
268 return wheel_position;
271 void wheel_send_events(bool send)
273 send_events = send;
275 #endif
277 #ifdef CPU_PP
278 void ipod_4g_button_int(void)
280 CPU_HI_INT_DIS = I2C_MASK;
282 /* The following delay was 250 in the ipodlinux source, but 50 seems to
283 work fine - tested on Nano, Color/Photo and Video. */
284 udelay(50);
286 int_btn = ipod_4g_button_read();
288 outl(inl(0x7000c104) | 0x0c000000, 0x7000c104);
289 outl(0x400a1f00, 0x7000c100);
291 CPU_HI_INT_EN = I2C_MASK;
294 void button_init_device(void)
296 opto_i2c_init();
298 /* hold button - enable as input */
299 GPIOA_ENABLE |= 0x20;
300 GPIOA_OUTPUT_EN &= ~0x20;
302 /* unmask interrupt */
303 CPU_INT_EN = HI_MASK;
304 CPU_HI_INT_EN = I2C_MASK;
307 bool button_hold(void)
309 return (GPIOA_INPUT_VAL & 0x20)?false:true;
312 bool headphones_inserted(void)
314 return (GPIOA_INPUT_VAL & 0x80)?true:false;
316 #else
317 void INT_SPI(void)
319 int clickwheel_events = CLICKWHEELINT;
321 /* Clear interrupts */
322 if (clickwheel_events & 4) CLICKWHEELINT = 4;
323 if (clickwheel_events & 2) CLICKWHEELINT = 2;
324 if (clickwheel_events & 1) CLICKWHEELINT = 1;
326 int_btn = ipod_4g_button_read();
329 void button_init_device(void)
331 CLICKWHEEL00 = 0x280000;
332 CLICKWHEEL10 = 3;
333 INTMOD = 0;
334 INTMSK |= (1<<26);
335 PCON10 &= ~0xF00;
338 bool button_hold(void)
340 return ((PDAT14 & (1 << 6)) == 0);
343 bool headphones_inserted(void)
345 return ((PDAT14 & (1 << 5)) != 0);
347 #endif
350 * Get button pressed from hardware
352 int button_read_device(void)
354 static bool hold_button = false;
355 bool hold_button_old;
357 /* normal buttons */
358 hold_button_old = hold_button;
359 hold_button = button_hold();
361 if (hold_button != hold_button_old)
363 backlight_hold_changed(hold_button);
365 if (hold_button)
367 #ifdef CPU_PP
368 /* lock -> disable wheel sensor */
369 DEV_EN &= ~DEV_OPTO;
370 #elif CONFIG_CPU==S5L8701
371 CLICKWHEEL00 = 0;
372 CLICKWHEEL10 = 0;
373 #endif
375 else
377 #ifdef CPU_PP
378 /* unlock -> enable wheel sensor */
379 DEV_EN |= DEV_OPTO;
380 opto_i2c_init();
381 #elif CONFIG_CPU==S5L8701
382 CLICKWHEEL00 = 0x280000;
383 CLICKWHEEL10 = 3;
384 #endif
388 /* The int_btn variable is set in the button interrupt handler */
389 #ifdef IPOD_ACCESSORY_PROTOCOL
390 return int_btn | remote_control_rx();
391 #else
392 return int_btn;
393 #endif