rockbox/firmware/target/arm/ipod/button-clickwheel.c
Steffen Butzer f5e2c25cb8 iPod Classic: Fix bidirectional clickwheel communication.
This restores functionality that was broken in g#194 and committed as
revision 7ec426e497.

Bidirectional communication is required to ask the clickwheel controller
for the initial button state during boot. Otherwise our driver would only
know about pressed buttons when the first change event is received,
which is too late for e.g. prevention of USB connection during boot.

This fix is also required to support the selection of OF, Rockbox,
Disk Mode, etc. in the iPod Classic Rockbox bootloader.

Change-Id: I127d54cf9e630d8075dd6d66f95dacb2816bfbc8
Reviewed-on: http://gerrit.rockbox.org/938
Reviewed-by: Michael Sparmann <theseven@gmx.net>
Tested: Michael Sparmann <theseven@gmx.net>
Reviewed-by: Marcin Bukat <marcin.bukat@gmail.com>
2014-10-02 12:56:37 +02:00

472 lines
15 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Daniel Stenberg
*
* iPod driver based on code from the ipodlinux project - http://ipodlinux.org
* Adapted for Rockbox in December 2005
* Original file: linux/arch/armnommu/mach-ipod/keyboard.c
* Copyright (c) 2003-2005 Bernard Leach (leachbj@bouncycastle.org)
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
/*
* Rockbox button functions
*/
#include <stdlib.h>
#include "config.h"
#include "cpu.h"
#include "system.h"
#include "button.h"
#include "kernel.h"
#include "backlight.h"
#include "serial.h"
#include "power.h"
#include "powermgmt.h"
#ifdef IPOD_NANO2G
#include "pmu-target.h"
#endif
#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 = 150ms */
#ifdef CPU_PP
#define CLICKWHEEL_DATA (*(volatile unsigned long*)(0x7000c140))
#elif CONFIG_CPU==S5L8701 || CONFIG_CPU==S5L8702
#define CLICKWHEEL_DATA WHEELRX
#else
#error CPU architecture not supported!
#endif
#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) || defined(IPOD_NANO2G)
#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;
static int repeat = 0;
int wheel_delta = 0;
bool wheel_is_touched = false;
unsigned int accumulated_wheel_delta = 0;
static unsigned int wheel_repeat = 0;
unsigned int wheel_velocity = 0;
static unsigned long last_wheel_usec = 0;
/* Variable to use for setting button status in interrupt handler */
static int int_btn = BUTTON_NONE;
#ifdef HAVE_WHEEL_POSITION
static int wheel_position = -1;
static bool send_events = true;
#endif
#if CONFIG_CPU==S5L8701 || CONFIG_CPU==S5L8702
static struct semaphore button_init_wakeup;
#endif
#ifdef CPU_PP
static void opto_i2c_init(void)
{
DEV_EN |= DEV_OPTO;
DEV_RS |= DEV_OPTO;
udelay(5);
DEV_RS &= ~DEV_OPTO; /* finish reset */
DEV_INIT1 |= INIT_BUTTONS; /* enable buttons (needed for "hold"-detection) */
outl(0xc00a1f00, 0x7000c100);
outl(0x01000000, 0x7000c104);
}
#endif
static inline int ipod_4g_button_read(void)
{
int whl = -1;
int btn = BUTTON_NONE;
#ifdef CPU_PP
if ((inl(0x7000c104) & 0x04000000) != 0)
{
#endif
unsigned status = CLICKWHEEL_DATA;
if ((status & 0x800000ff) == 0x8000001a)
{
if (status & 0x00000100)
btn |= BUTTON_SELECT;
if (status & 0x00000200)
btn |= BUTTON_RIGHT;
if (status & 0x00000400)
btn |= BUTTON_LEFT;
if (status & 0x00000800)
btn |= BUTTON_PLAY;
if (status & 0x00001000)
btn |= BUTTON_MENU;
if (status & 0x40000000)
{
unsigned long usec = USEC_TIMER;
/* Highest wheel = 0x5F, clockwise increases */
new_wheel_value = (status >> 16) & 0x7f;
whl = new_wheel_value;
/* switch on backlight (again), reset power-off timer */
backlight_on();
reset_poweroff_timer();
/* Check whether the scrollwheel was untouched by accident or by will. */
/* This is needed because wheel may be untoched very shortly during rotation */
if ( (!wheel_is_touched) && TIME_AFTER(usec, last_wheel_usec + WHEEL_UNTOUCH_TIMEOUT) )
{
/* wheel has been really untouched -> reset internal variables */
old_wheel_value = -1;
wheel_velocity = 0;
accumulated_wheel_delta = 0;
wheel_repeat = BUTTON_NONE;
}
else
{
/* wheel was shortly untouched by accident -> leave internal variables */
wheel_is_touched = true;
}
if (old_wheel_value >= 0)
{
/* This is for later = BUTTON_SCROLL_TOUCH;*/
wheel_delta = new_wheel_value - old_wheel_value;
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 = (15 * wheel_velocity + v) / 16;
/* 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;
}
}
#if CONFIG_CPU==S5L8701 || CONFIG_CPU==S5L8702
else if ((status & 0x8000FFFF) == 0x8000023A)
{
if (status & 0x00010000)
btn |= BUTTON_SELECT;
if (status & 0x00020000)
btn |= BUTTON_RIGHT;
if (status & 0x00040000)
btn |= BUTTON_LEFT;
if (status & 0x00080000)
btn |= BUTTON_PLAY;
if (status & 0x00100000)
btn |= BUTTON_MENU;
semaphore_release(&button_init_wakeup);
}
#endif
#ifdef CPU_PP
}
#endif
#ifdef HAVE_WHEEL_POSITION
/* Save the new absolute wheel position */
wheel_position = whl;
#endif
return btn;
}
#ifdef HAVE_WHEEL_POSITION
int wheel_status(void)
{
return wheel_position;
}
void wheel_send_events(bool send)
{
send_events = send;
}
#endif
#ifdef CPU_PP
void ipod_4g_button_int(void)
{
CPU_HI_INT_DIS = I2C_MASK;
/* The following delay was 250 in the ipodlinux source, but 50 seems to
work fine - tested on Nano, Color/Photo and Video. */
udelay(50);
int_btn = ipod_4g_button_read();
outl(inl(0x7000c104) | 0x0c000000, 0x7000c104);
outl(0x400a1f00, 0x7000c100);
CPU_HI_INT_EN = I2C_MASK;
}
void button_init_device(void)
{
opto_i2c_init();
/* hold button - enable as input */
GPIOA_ENABLE |= 0x20;
GPIOA_OUTPUT_EN &= ~0x20;
/* unmask interrupt */
CPU_INT_EN = HI_MASK;
CPU_HI_INT_EN = I2C_MASK;
}
bool button_hold(void)
{
return (GPIOA_INPUT_VAL & 0x20)?false:true;
}
bool headphones_inserted(void)
{
return (GPIOA_INPUT_VAL & 0x80)?true:false;
}
#else
void INT_WHEEL(void)
{
int clickwheel_events = WHEELINT;
/* Clear interrupts */
if (clickwheel_events & 4) WHEELINT = 4;
if (clickwheel_events & 2) WHEELINT = 2;
if (clickwheel_events & 1) WHEELINT = 1;
int_btn = ipod_4g_button_read();
}
static void s5l_clickwheel_init(void)
{
#if CONFIG_CPU==S5L8701
PWRCONEXT &= ~1;
PCON15 = (PCON15 & ~0xFFFF0000) | 0x22220000;
PUNK15 = 0xF0;
WHEEL08 = 0x3A980;
WHEEL00 = 0x280000;
WHEEL10 = 3;
PCON10 = (PCON10 & ~0xFF0) | 0x10;
PDAT10 |= 2;
WHEELTX = 0x8000023A;
WHEEL04 |= 1;
PDAT10 &= ~2;
#elif CONFIG_CPU==S5L8702
/* enable and init internal (s5l8702) wheel controller */
PWRCON(1) &= ~(1 << 1);
PCON(14) = (PCON(14) & ~0xffff0000) | 0x22220000;
WHEELINT = 7;
WHEEL10 = 1;
WHEEL00 = 0x380000;
WHEEL08 = 0x20000;
WHEELTX = 0x8000023A;
WHEEL04 |= 1;
#endif
}
void button_init_device(void)
{
semaphore_init(&button_init_wakeup, 1, 0);
#if CONFIG_CPU==S5L8701
INTMSK |= (1<<26);
#elif CONFIG_CPU==S5L8702
/* configure GPIO E2 as pull-up input */
PUNB(14) |= (1 << 2);
#endif
s5l_clickwheel_init();
semaphore_wait(&button_init_wakeup, HZ / 10);
}
bool button_hold(void)
{
#if CONFIG_CPU==S5L8701
bool value = (PDAT14 & (1 << 6)) == 0;
if (value)
PCON15 = PCON15 & ~0xffff0000;
else PCON15 = (PCON15 & ~0xffff0000) | 0x22220000;
return value;
#elif CONFIG_CPU==S5L8702
return ((PDATE & (1 << 2)) == 0);
#endif
}
bool headphones_inserted(void)
{
#if CONFIG_CPU==S5L8701
return ((PDAT14 & (1 << 5)) != 0);
#elif CONFIG_CPU==S5L8702
return ((PDATA & (1 << 6)) != 0);
#endif
}
#endif
/*
* Get button pressed from hardware
*/
int button_read_device(void)
{
static bool hold_button = false;
bool hold_button_old;
/* normal buttons */
hold_button_old = hold_button;
hold_button = button_hold();
if (hold_button != hold_button_old)
{
#ifndef BOOTLOADER
backlight_hold_changed(hold_button);
#endif
if (hold_button)
{
#ifdef CPU_PP
/* lock -> disable wheel sensor */
DEV_EN &= ~DEV_OPTO;
#elif CONFIG_CPU==S5L8701
pmu_ldo_power_off(1); /* disable clickwheel power supply */
WHEEL00 = 0;
WHEEL10 = 0;
PWRCONEXT |= 1;
#elif CONFIG_CPU==S5L8702
/* disable external (CY8C21x34) wheel controller */
GPIOCMD = 0xe040e;
/* disable internal (s5l8702) wheel controller */
WHEEL00 = 0;
WHEEL10 = 0;
PWRCON(1) |= (1 << 1);
#endif
}
else
{
#ifdef CPU_PP
/* unlock -> enable wheel sensor */
DEV_EN |= DEV_OPTO;
opto_i2c_init();
#elif CONFIG_CPU==S5L8701
pmu_ldo_power_on(1); /* enable clickwheel power supply */
s5l_clickwheel_init();
#elif CONFIG_CPU==S5L8702
s5l_clickwheel_init();
#endif
}
}
/* The int_btn variable is set in the button interrupt handler */
#ifdef IPOD_ACCESSORY_PROTOCOL
return int_btn | remote_control_rx();
#else
return int_btn;
#endif
}