rockbox/firmware/drivers/button.c
Solomon Peachy 13dbcab6c0 erosq: When mucking with the clickwheel, ensure we keep the screen awake!
Change-Id: I49d39f301f4b44c2477a657e2af964b97d73cf6b
2021-04-09 19:21:02 -04:00

788 lines
21 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Daniel Stenberg
*
* 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 "system.h"
#include "button.h"
#include "kernel.h"
#include "thread.h"
#include "backlight.h"
#include "serial.h"
#include "power.h"
#include "powermgmt.h"
#ifdef HAVE_SDL
#include "button-sdl.h"
#else
#include "button-target.h"
#endif
#ifdef HAVE_REMOTE_LCD
#include "lcd-remote.h"
#endif
#if defined(HAVE_TRANSFLECTIVE_LCD) && defined(HAVE_LCD_SLEEP)
#include "lcd.h" /* lcd_active() prototype */
#endif
struct event_queue button_queue SHAREDBSS_ATTR;
static long lastbtn; /* Last valid button status */
static long last_read; /* Last button status, for debouncing/filtering */
static intptr_t button_data; /* data value from last message dequeued */
static bool flipped; /* buttons can be flipped to match the LCD flip */
#ifdef HAVE_BACKLIGHT
static bool filter_first_keypress;
#ifdef HAVE_REMOTE_LCD
static bool remote_filter_first_keypress;
#endif
#endif /* HAVE_BACKLIGHT */
#ifdef HAVE_HEADPHONE_DETECTION
static bool phones_present = false;
#endif
#ifdef HAVE_LINEOUT_DETECTION
static bool lineout_present = false;
#endif
#ifdef HAVE_SW_POWEROFF
static bool enable_sw_poweroff = true;
#endif
/* how long until repeat kicks in, in centiseconds */
#define REPEAT_START (30*HZ/100)
#ifndef HAVE_TOUCHSCREEN
/* the next two make repeat "accelerate", which is nice for lists
* which begin to scroll a bit faster when holding until the
* real list accerelation kicks in (this smoothes acceleration)
*/
/* the speed repeat starts at, in centiseconds */
#define REPEAT_INTERVAL_START (16*HZ/100)
/* speed repeat finishes at, in centiseconds */
#define REPEAT_INTERVAL_FINISH (5*HZ/100)
#else
/*
* on touchscreen it's different, scrolling is done by swiping over the
* screen (potentially very quickly) and is completely different from button
* targets
* So, on touchscreen we don't want to artifically slow down early repeats,
* it'd have the contrary effect of making rockbox appear lagging
*/
#define REPEAT_INTERVAL_START (5*HZ/100)
#define REPEAT_INTERVAL_FINISH (5*HZ/100)
#endif
#ifdef HAVE_BUTTON_DATA
static int button_read(int *data);
static int lastdata = 0;
#else
static int button_read(void);
#endif
#ifdef HAVE_TOUCHSCREEN
static int last_touchscreen_touch;
#endif
#if defined(HAVE_HEADPHONE_DETECTION)
static struct timeout hp_detect_timeout; /* Debouncer for headphone plug/unplug */
static int hp_detect_callback(struct timeout *tmo)
{
/* Try to post only transistions */
const long id = tmo->data ? SYS_PHONE_PLUGGED : SYS_PHONE_UNPLUGGED;
queue_remove_from_head(&button_queue, id);
queue_post(&button_queue, id, 0);
return 0;
/*misc.c:hp_unplug_change*/
}
#endif
#if defined(HAVE_LINEOUT_DETECTION)
static struct timeout lo_detect_timeout; /* Debouncer for lineout plug/unplug */
static int lo_detect_callback(struct timeout *tmo)
{
/* Try to post only transistions */
const long id = tmo->data ? SYS_LINEOUT_PLUGGED : SYS_LINEOUT_UNPLUGGED;
queue_remove_from_head(&button_queue, id);
queue_post(&button_queue, id, 0);
return 0;
/*misc.c:lo_unplug_change*/
}
#endif
static bool button_try_post(int button, int data)
{
#ifdef HAVE_TOUCHSCREEN
/* one can swipe over the scren very quickly,
* for this to work we want to forget about old presses and
* only respect the very latest ones */
const bool force_post = true;
#else
/* Only post events if the queue is empty,
* to avoid afterscroll effects.
* i.e. don't post new buttons if previous ones haven't been
* processed yet - but always post releases */
const bool force_post = button & BUTTON_REL;
#endif
bool ret = queue_empty(&button_queue);
if (!ret && force_post)
{
queue_remove_from_head(&button_queue, button);
ret = true;
}
if (ret)
queue_post(&button_queue, button, data);
/* on touchscreen we posted unconditionally */
return ret;
}
static void button_tick(void)
{
static int count = 0;
static int repeat_speed = REPEAT_INTERVAL_START;
static int repeat_count = 0;
static bool repeat = false;
static bool post = false;
#ifdef HAVE_BACKLIGHT
static bool skip_release = false;
#ifdef HAVE_REMOTE_LCD
static bool skip_remote_release = false;
#endif
#endif
int diff;
int btn;
#ifdef HAVE_BUTTON_DATA
int data = 0;
#else
const int data = 0;
#endif
#if defined(HAS_SERIAL_REMOTE) && !defined(SIMULATOR)
/* Post events for the remote control */
btn = remote_control_rx();
if(btn)
button_try_post(btn, 0);
#endif
#ifdef HAVE_BUTTON_DATA
btn = button_read(&data);
#else
btn = button_read();
#endif
#if defined(HAVE_HEADPHONE_DETECTION)
if (headphones_inserted() != phones_present)
{
/* Use the autoresetting oneshot to debounce the detection signal */
phones_present = !phones_present;
timeout_register(&hp_detect_timeout, hp_detect_callback,
HZ/2, phones_present);
}
#endif
#if defined(HAVE_LINEOUT_DETECTION)
if (lineout_inserted() != lineout_present)
{
/* Use the autoresetting oneshot to debounce the detection signal */
lineout_present = !lineout_present;
timeout_register(&lo_detect_timeout, lo_detect_callback,
HZ/2, lineout_present);
}
#endif
/* Find out if a key has been released */
diff = btn ^ lastbtn;
if(diff && (btn & diff) == 0)
{
#ifdef HAVE_BACKLIGHT
#ifdef HAVE_REMOTE_LCD
if(diff & BUTTON_REMOTE)
if(!skip_remote_release)
button_try_post(BUTTON_REL | diff, data);
else
skip_remote_release = false;
else
#endif
if(!skip_release)
button_try_post(BUTTON_REL | diff, data);
else
skip_release = false;
#else
button_try_post(BUTTON_REL | diff, data);
#endif
}
else
{
if ( btn )
{
/* normal keypress */
if ( btn != lastbtn )
{
post = true;
repeat = false;
repeat_speed = REPEAT_INTERVAL_START;
}
else /* repeat? */
{
#if defined(DX50) || defined(DX90)
/*
Power button on these devices reports two distinct key codes, which are
triggerd by a short or medium duration press. Additionlly a long duration press
will trigger a hard reset, which is hardwired.
The time delta between medium and long duration press is not large enough to
register here as power off repeat. A hard reset is triggered before Rockbox
can power off.
To cirumvent the hard reset, Rockbox will shutdown on the first POWEROFF_BUTTON
repeat. POWEROFF_BUTTON is associated with the a medium duration press of the
power button.
*/
if(btn & POWEROFF_BUTTON)
{
sys_poweroff();
}
#endif
if ( repeat )
{
if (!post)
count--;
if (count == 0) {
post = true;
/* yes we have repeat */
if (repeat_speed > REPEAT_INTERVAL_FINISH)
repeat_speed--;
count = repeat_speed;
repeat_count++;
/* Send a SYS_POWEROFF event if we have a device
which doesn't shut down easily with the OFF
key */
#ifdef HAVE_SW_POWEROFF
if (enable_sw_poweroff &&
(btn & POWEROFF_BUTTON
#ifdef RC_POWEROFF_BUTTON
|| btn == RC_POWEROFF_BUTTON
#endif
) &&
#if CONFIG_CHARGING && !defined(HAVE_POWEROFF_WHILE_CHARGING)
!charger_inserted() &&
#endif
repeat_count > POWEROFF_COUNT)
{
/* Tell the main thread that it's time to
power off */
sys_poweroff();
/* Safety net for players without hardware
poweroff */
#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
if(repeat_count > POWEROFF_COUNT * 10)
power_off();
#endif
}
#endif
}
}
else
{
if (count++ > REPEAT_START)
{
post = true;
repeat = true;
repeat_count = 0;
/* initial repeat */
count = REPEAT_INTERVAL_START;
}
#ifdef HAVE_TOUCHSCREEN
else if (lastdata != data && btn == lastbtn)
{ /* only coordinates changed, post anyway */
if (touchscreen_get_mode() == TOUCHSCREEN_POINT)
post = true;
}
#endif
}
}
if ( post )
{
if (repeat)
{
/* Only post repeat events if the queue is empty,
* to avoid afterscroll effects. */
if (button_try_post(BUTTON_REPEAT | btn, data))
{
#ifdef HAVE_BACKLIGHT
#ifdef HAVE_REMOTE_LCD
skip_remote_release = false;
#endif
skip_release = false;
#endif
post = false;
}
}
else
{
#ifdef HAVE_BACKLIGHT
#ifdef HAVE_REMOTE_LCD
if (btn & BUTTON_REMOTE) {
if (!remote_filter_first_keypress
|| is_remote_backlight_on(false)
#if defined(IRIVER_H100_SERIES) || defined(IRIVER_H300_SERIES)
|| (remote_type()==REMOTETYPE_H300_NONLCD)
#endif
)
button_try_post(btn, data);
else
skip_remote_release = true;
}
else
#endif
if (!filter_first_keypress
#if defined(HAVE_TRANSFLECTIVE_LCD) && defined(HAVE_LCD_SLEEP)
|| (is_backlight_on(false) && lcd_active())
#else
|| is_backlight_on(false)
#endif
#if BUTTON_REMOTE
|| (btn & BUTTON_REMOTE)
#endif
)
button_try_post(btn, data);
else
skip_release = true;
#else /* no backlight, nothing to skip */
button_try_post(btn, data);
#endif
post = false;
}
#ifdef HAVE_REMOTE_LCD
if(btn & BUTTON_REMOTE)
remote_backlight_on();
else
#endif
{
backlight_on();
#ifdef HAVE_BUTTON_LIGHT
buttonlight_on();
#endif
}
reset_poweroff_timer();
}
}
else
{
repeat = false;
count = 0;
}
}
lastbtn = btn & ~(BUTTON_REL | BUTTON_REPEAT);
#ifdef HAVE_BUTTON_DATA
lastdata = data;
#endif
}
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
static bool button_boosted = false;
static long button_unboost_tick;
#define BUTTON_UNBOOST_TMO HZ
static void button_boost(bool state)
{
if (state)
{
button_unboost_tick = current_tick + BUTTON_UNBOOST_TMO;
if (!button_boosted)
{
button_boosted = true;
cpu_boost(true);
}
}
else if (!state && button_boosted)
{
button_boosted = false;
cpu_boost(false);
}
}
static void button_queue_wait(struct queue_event *evp, int timeout)
{
/* Loop once after wait time if boosted in order to unboost and wait the
full remaining time */
do
{
int ticks = timeout;
if (ticks == 0) /* TIMEOUT_NOBLOCK */
;
else if (ticks > 0)
{
if (button_boosted && ticks > BUTTON_UNBOOST_TMO)
ticks = BUTTON_UNBOOST_TMO;
timeout -= ticks;
}
else /* TIMEOUT_BLOCK (ticks < 0) */
{
if (button_boosted)
ticks = BUTTON_UNBOOST_TMO;
}
queue_wait_w_tmo(&button_queue, evp, ticks);
if (evp->id != SYS_TIMEOUT)
{
/* GUI boost build gets immediate kick, otherwise at least 3
messages had to be there */
#ifndef HAVE_GUI_BOOST
if (queue_count(&button_queue) >= 2)
#endif
button_boost(true);
break;
}
if (button_boosted && TIME_AFTER(current_tick, button_unboost_tick))
button_boost(false);
}
while (timeout);
}
#else /* ndef HAVE_ADJUSTABLE_CPU_FREQ */
static inline void button_queue_wait(struct queue_event *evp, int timeout)
{
queue_wait_w_tmo(&button_queue, evp, timeout);
}
#endif /* HAVE_ADJUSTABLE_CPU_FREQ */
int button_queue_count( void )
{
return queue_count(&button_queue);
}
long button_get(bool block)
{
struct queue_event ev;
button_queue_wait(&ev, block ? TIMEOUT_BLOCK : TIMEOUT_NOBLOCK);
if (ev.id == SYS_TIMEOUT)
ev.id = BUTTON_NONE;
else
button_data = ev.data;
return ev.id;
}
long button_get_w_tmo(int ticks)
{
struct queue_event ev;
button_queue_wait(&ev, ticks);
if (ev.id == SYS_TIMEOUT)
ev.id = BUTTON_NONE;
else
button_data = ev.data;
return ev.id;
}
intptr_t button_get_data(void)
{
return button_data;
}
void button_init(void)
{
/* Init used objects first */
queue_init(&button_queue, true);
#ifdef HAVE_BUTTON_DATA
int temp;
#endif
/* hardware inits */
button_init_device();
#ifdef HAVE_BUTTON_DATA
button_read(&temp);
lastbtn = button_read(&temp);
#else
button_read();
lastbtn = button_read();
#endif
reset_poweroff_timer();
flipped = false;
#ifdef HAVE_BACKLIGHT
filter_first_keypress = false;
#ifdef HAVE_REMOTE_LCD
remote_filter_first_keypress = false;
#endif
#endif
#ifdef HAVE_TOUCHSCREEN
last_touchscreen_touch = 0xffff;
#endif
/* Start polling last */
tick_add_task(button_tick);
}
#ifdef BUTTON_DRIVER_CLOSE
void button_close(void)
{
tick_remove_task(button_tick);
}
#endif /* BUTTON_DRIVER_CLOSE */
#ifdef HAVE_LCD_FLIP
/*
* helper function to swap LEFT/RIGHT, UP/DOWN (if present)
*/
static int button_flip(int button)
{
int newbutton = button;
#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
newbutton &= ~(
#if defined(BUTTON_LEFT) && defined(BUTTON_RIGHT)
BUTTON_LEFT | BUTTON_RIGHT
#else
#warning "LEFT/RIGHT not defined!"
0
#endif
#if defined(BUTTON_UP) && defined(BUTTON_DOWN)
| BUTTON_UP | BUTTON_DOWN
#endif
#if defined(BUTTON_SCROLL_BACK) && defined(BUTTON_SCROLL_FWD)
| BUTTON_SCROLL_BACK | BUTTON_SCROLL_FWD
#endif
#if (CONFIG_KEYPAD == SANSA_C200_PAD) || (CONFIG_KEYPAD == SANSA_CLIP_PAD) ||\
(CONFIG_KEYPAD == GIGABEAT_PAD) || (CONFIG_KEYPAD == GIGABEAT_S_PAD)
| BUTTON_VOL_UP | BUTTON_VOL_DOWN
#endif
#if CONFIG_KEYPAD == PHILIPS_SA9200_PAD
| BUTTON_VOL_UP | BUTTON_VOL_DOWN
| BUTTON_NEXT | BUTTON_PREV
#endif
);
#if defined(BUTTON_LEFT) && defined(BUTTON_RIGHT)
if (button & BUTTON_LEFT)
newbutton |= BUTTON_RIGHT;
if (button & BUTTON_RIGHT)
newbutton |= BUTTON_LEFT;
#else
#warning "LEFT/RIGHT not defined!"
#endif
#if defined(BUTTON_UP) && defined(BUTTON_DOWN)
if (button & BUTTON_UP)
newbutton |= BUTTON_DOWN;
if (button & BUTTON_DOWN)
newbutton |= BUTTON_UP;
#endif
#if defined(BUTTON_SCROLL_BACK) && defined(BUTTON_SCROLL_FWD)
if (button & BUTTON_SCROLL_BACK)
newbutton |= BUTTON_SCROLL_FWD;
if (button & BUTTON_SCROLL_FWD)
newbutton |= BUTTON_SCROLL_BACK;
#endif
#if (CONFIG_KEYPAD == SANSA_C200_PAD) || (CONFIG_KEYPAD == SANSA_CLIP_PAD) ||\
(CONFIG_KEYPAD == GIGABEAT_PAD) || (CONFIG_KEYPAD == GIGABEAT_S_PAD)
if (button & BUTTON_VOL_UP)
newbutton |= BUTTON_VOL_DOWN;
if (button & BUTTON_VOL_DOWN)
newbutton |= BUTTON_VOL_UP;
#endif
#if CONFIG_KEYPAD == PHILIPS_SA9200_PAD
if (button & BUTTON_VOL_UP)
newbutton |= BUTTON_VOL_DOWN;
if (button & BUTTON_VOL_DOWN)
newbutton |= BUTTON_VOL_UP;
if (button & BUTTON_NEXT)
newbutton |= BUTTON_PREV;
if (button & BUTTON_PREV)
newbutton |= BUTTON_NEXT;
#endif
#endif /* !SIMULATOR */
return newbutton;
}
/*
* set the flip attribute
* better only call this when the queue is empty
*/
void button_set_flip(bool flip)
{
if (flip != flipped) /* not the current setting */
{
/* avoid race condition with the button_tick() */
int oldlevel = disable_irq_save();
lastbtn = button_flip(lastbtn);
flipped = flip;
restore_irq(oldlevel);
}
}
#endif /* HAVE_LCD_FLIP */
#ifdef HAVE_BACKLIGHT
void set_backlight_filter_keypress(bool value)
{
filter_first_keypress = value;
}
#ifdef HAVE_REMOTE_LCD
void set_remote_backlight_filter_keypress(bool value)
{
remote_filter_first_keypress = value;
}
#endif
#endif
/*
* Get button pressed from hardware
*/
#ifdef HAVE_BUTTON_DATA
static int button_read(int *data)
{
int btn = button_read_device(data);
#else
static int button_read(void)
{
int btn = button_read_device();
#endif
int retval;
#ifdef HAVE_LCD_FLIP
if (btn && flipped)
btn = button_flip(btn); /* swap upside down */
#endif /* HAVE_LCD_FLIP */
#ifdef HAVE_TOUCHSCREEN
if (btn & BUTTON_TOUCHSCREEN)
last_touchscreen_touch = current_tick;
#endif
/* Filter the button status. It is only accepted if we get the same
status twice in a row. */
#ifndef HAVE_TOUCHSCREEN
if (btn != last_read)
retval = lastbtn;
else
#endif
retval = btn;
last_read = btn;
return retval;
}
int button_status(void)
{
return lastbtn;
}
#ifdef HAVE_BUTTON_DATA
int button_status_wdata(int *pdata)
{
*pdata = lastdata;
return lastbtn;
}
#endif
void button_clear_queue(void)
{
queue_clear(&button_queue);
}
#ifdef HAVE_TOUCHSCREEN
int touchscreen_last_touch(void)
{
return last_touchscreen_touch;
}
#endif
#ifdef HAVE_WHEEL_ACCELERATION
/* WHEEL_ACCEL_FACTOR = 2^16 / WHEEL_ACCEL_START */
#define WHEEL_ACCEL_FACTOR (1<<16)/WHEEL_ACCEL_START
/**
* data:
* [31] Use acceleration
* [30:24] Message post count (skipped + 1) (1-127)
* [23:0] Velocity - degree/sec
*
* WHEEL_ACCEL_FACTOR:
* Value in degree/sec -- configurable via settings -- above which
* the accelerated scrolling starts. Factor is internally scaled by
* 1<<16 in respect to the following 32bit integer operations.
*/
int button_apply_acceleration(const unsigned int data)
{
int delta = (data >> 24) & 0x7f;
if ((data & (1 << 31)) != 0)
{
/* read driver's velocity from data */
unsigned int v = data & 0xffffff;
/* 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)
delta *= v;
}
return delta;
}
#endif /* HAVE_WHEEL_ACCELERATION */
#if (defined(HAVE_TOUCHPAD) || defined(HAVE_TOUCHSCREEN)) && !defined(HAS_BUTTON_HOLD)
void button_enable_touch(bool en)
{
#ifdef HAVE_TOUCHPAD
touchpad_enable(en);
#endif
#ifdef HAVE_TOUCHSCREEN
touchscreen_enable(en);
#endif
}
#endif
#ifdef HAVE_SW_POWEROFF
void button_set_sw_poweroff_state(bool en) {
enable_sw_poweroff = en;
}
bool button_get_sw_poweroff_state() {
return enable_sw_poweroff;
}
#endif