rockbox/firmware/target/arm/ipod/button-clickwheel.c
Cástor Muñoz 348bfc5c8f iPod Classic: clickwheel fixes
Uses GPIO.E2 (Request To Send) to detect the holdswitch status,
it is a temporal workaround that seems to work on all models.

Holdswitch status must be detected to drive low GPIO.E2 (RTS)
and GPIO.E4 (Data Out) when the holdswitch is locked, otherwise
battery life decreases about 25%.

Holdswitch unlock action is detected by reading the HELLO message
that the external wheel controller sends when it is powered on,
this allows to quickly capture clickwheel activity after unlock.
GPIO.E2 is also used in case the HELLO message is missed because
the holdswitch was unlocked before Rockbox/bootloader starts.

These 2 lines (RTS and DOUT) can not be used to transmit messages
to the external clickwheel controller, not a problem, actually no
messages are sent while normal operation, only at initialization
stage.

Change-Id: I415fe54bfcbc2086d0f56d7affe6f789ce81a6db
2015-12-17 10:26:51 +01:00

477 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
#if CONFIG_CPU==S5L8702
else if (status == 0xAAAAAAAA)
{
GPIOCMD = 0xe040f; /* DOUT = Output High */
}
#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
PWRCON(1) &= ~(1 << 1); /* unmask clockgate */
WHEEL00 = 0; /* stop s5l8702 controller */
PUNB(14) &= ~(1 << 2); /* disable pull-up for GPIO E2 */
udelay(100);
PCON(14) = (PCON(14) & ~0x00ffff00) | 0x00222200;
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);
#endif
s5l_clickwheel_init();
semaphore_wait(&button_init_wakeup, HZ / 10);
#if CONFIG_CPU==S5L8702
/* configure GPIO E2 as pull-up input */
PUNB(14) |= (1 << 2);
udelay(100);
GPIOCMD = 0xe0200;
#endif
}
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
GPIOCMD = 0xe040e; /* DOUT = Output Low */
#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
GPIOCMD = 0xe040f; /* DOUT = Output High */
#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
}