From 753b7a90e192dbfd1b268a78e200c103dcca80da Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Fri, 23 Jan 2009 20:55:17 +0000 Subject: [PATCH] Gigabeat S: Add remote control reading and proper headphone insert detection. We need keymaps! A few were copied straight from Gigabeat F/X just to get things worked out. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@19830 a1c6a512-1295-4272-9138-f99709370657 --- apps/keymaps/keymap-gigabeat-s.c | 64 ++++++ firmware/SOURCES | 1 + firmware/export/config-gigabeat-s.h | 2 +- .../arm/imx31/gigabeat-s/button-imx31.c | 31 +-- .../arm/imx31/gigabeat-s/button-target.h | 14 +- .../imx31/gigabeat-s/headphone-gigabeat-s.c | 201 ++++++++++++++++++ 6 files changed, 290 insertions(+), 23 deletions(-) create mode 100644 firmware/target/arm/imx31/gigabeat-s/headphone-gigabeat-s.c diff --git a/apps/keymaps/keymap-gigabeat-s.c b/apps/keymaps/keymap-gigabeat-s.c index 32c4f22b5b..d32177ea90 100644 --- a/apps/keymaps/keymap-gigabeat-s.c +++ b/apps/keymaps/keymap-gigabeat-s.c @@ -309,8 +309,72 @@ static const struct button_mapping button_context_radio[] = { LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_SETTINGS) }; +/***************************************************************************** + * Remote control mappings + *****************************************************************************/ +static const struct button_mapping remote_button_context_standard[] = { + { ACTION_STD_PREV, BUTTON_RC_VOL_UP, BUTTON_NONE }, + { ACTION_STD_PREVREPEAT, BUTTON_RC_VOL_UP|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_STD_NEXT, BUTTON_RC_VOL_DOWN, BUTTON_NONE }, + { ACTION_STD_NEXTREPEAT, BUTTON_RC_VOL_DOWN|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_STD_CANCEL, BUTTON_RC_REW, BUTTON_NONE }, + { ACTION_STD_OK, BUTTON_RC_FF|BUTTON_REL, BUTTON_RC_FF }, + { ACTION_STD_CONTEXT, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_RC_FF }, + { ACTION_STD_MENU, BUTTON_RC_DSP, BUTTON_NONE }, + + LAST_ITEM_IN_LIST +}; + +static const struct button_mapping remote_button_context_wps[] = { + { ACTION_WPS_PLAY, BUTTON_RC_PLAY, BUTTON_NONE }, + + { ACTION_WPS_SKIPNEXT, BUTTON_RC_FF|BUTTON_REL, BUTTON_RC_FF }, + { ACTION_WPS_SKIPPREV, BUTTON_RC_REW|BUTTON_REL, BUTTON_RC_REW }, + + { ACTION_WPS_SEEKBACK, BUTTON_RC_REW|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_WPS_SEEKFWD, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_WPS_STOPSEEK, BUTTON_RC_REW|BUTTON_REL, BUTTON_RC_REW|BUTTON_REPEAT }, + { ACTION_WPS_STOPSEEK, BUTTON_RC_FF|BUTTON_REL, BUTTON_RC_FF|BUTTON_REPEAT }, + + { ACTION_WPS_STOP, BUTTON_RC_PLAY|BUTTON_REPEAT, BUTTON_RC_PLAY }, + { ACTION_WPS_MENU, BUTTON_RC_DSP, BUTTON_NONE }, + + { ACTION_WPS_VOLDOWN, BUTTON_RC_VOL_DOWN, BUTTON_NONE }, + { ACTION_WPS_VOLDOWN, BUTTON_RC_VOL_DOWN|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_WPS_VOLUP, BUTTON_RC_VOL_UP|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_WPS_VOLUP, BUTTON_RC_VOL_UP, BUTTON_NONE }, + + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) +}; + +static const struct button_mapping remote_button_context_tree[] = { + { ACTION_TREE_WPS, BUTTON_RC_PLAY|BUTTON_REL, BUTTON_RC_PLAY }, + { ACTION_TREE_STOP, BUTTON_RC_PLAY|BUTTON_REPEAT, BUTTON_RC_PLAY }, + { ACTION_STD_CANCEL, BUTTON_RC_REW|BUTTON_REPEAT, BUTTON_NONE }, + + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) +}; + +static const struct button_mapping* get_context_mapping_remote( int context ) +{ + context ^= CONTEXT_REMOTE; + + switch (context) + { + case CONTEXT_WPS: + return remote_button_context_wps; + case CONTEXT_MAINMENU: + case CONTEXT_TREE: + return remote_button_context_tree; + } + return remote_button_context_standard; +} + const struct button_mapping* get_context_mapping(int context) { + if (context&CONTEXT_REMOTE) + return get_context_mapping_remote(context); + switch (context) { case CONTEXT_STD: diff --git a/firmware/SOURCES b/firmware/SOURCES index bce384e1c8..c8c38ceee2 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -774,6 +774,7 @@ target/arm/imx31/gigabeat-s/wmcodec-imx31.c #ifndef BOOTLOADER target/arm/imx31/gigabeat-s/audio-gigabeat-s.c target/arm/imx31/gigabeat-s/fmradio-i2c-gigabeat-s.c +target/arm/imx31/gigabeat-s/headphone-gigabeat-s.c target/arm/imx31/gigabeat-s/pcm-imx31.c target/arm/imx31/gigabeat-s/timer-imx31.c #endif diff --git a/firmware/export/config-gigabeat-s.h b/firmware/export/config-gigabeat-s.h index 9e6029f00b..93068ae441 100644 --- a/firmware/export/config-gigabeat-s.h +++ b/firmware/export/config-gigabeat-s.h @@ -140,7 +140,7 @@ #define GPIO_EVENT_MASK (USE_GPIO1_EVENTS) /* Define this if target has an additional number of threads specific to it */ -#define TARGET_EXTRA_THREADS 1 +#define TARGET_EXTRA_THREADS 2 /* Type of mobile power - check this out */ #define BATTERY_CAPACITY_DEFAULT 700 /* default battery capacity */ diff --git a/firmware/target/arm/imx31/gigabeat-s/button-imx31.c b/firmware/target/arm/imx31/gigabeat-s/button-imx31.c index 587e66e0bc..dec0aa108f 100644 --- a/firmware/target/arm/imx31/gigabeat-s/button-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/button-imx31.c @@ -32,9 +32,6 @@ /* Most code in here is taken from the Linux BSP provided by Freescale * Copyright 2004-2006 Freescale Semiconductor, Inc. All Rights Reserved. */ -#ifdef HAVE_HEADPHONE_DETECTION -static bool headphones_detect = false; -#endif static uint32_t int_btn = BUTTON_NONE; static bool hold_button = false; #ifdef BOOTLOADER @@ -123,6 +120,16 @@ bool button_hold(void) return _button_hold(); } +#ifdef HAVE_HEADPHONE_DETECTION +/* Headphone driver pushes the data here */ +void button_headphone_set(int button) +{ + int oldstatus = disable_irq_save(); + int_btn = (int_btn & ~BUTTON_REMOTE) | button; + restore_irq(oldstatus); +} +#endif + int button_read_device(void) { /* Simple poll of GPIO status */ @@ -168,21 +175,6 @@ void button_power_event(void) restore_irq(oldlevel); } -#ifdef HAVE_HEADPHONE_DETECTION -/* This is called from the mc13783 interrupt thread */ -void headphone_detect_event(void) -{ - /* FIXME: Not really the correct method */ - headphones_detect = - (mc13783_read(MC13783_INTERRUPT_SENSE1) & MC13783_ONOFD2S) == 0; -} - -bool headphones_inserted(void) -{ - return headphones_detect; -} -#endif /* HAVE_HEADPHONE_DETECTION */ - void button_init_device(void) { #ifdef BOOTLOADER @@ -223,8 +215,7 @@ void button_init_device(void) mc13783_enable_event(MC13783_ONOFD1_EVENT); #ifdef HAVE_HEADPHONE_DETECTION - headphone_detect_event(); - mc13783_enable_event(MC13783_ONOFD2_EVENT); + headphone_init(); #endif } diff --git a/firmware/target/arm/imx31/gigabeat-s/button-target.h b/firmware/target/arm/imx31/gigabeat-s/button-target.h index 754694eee5..d970e9983c 100644 --- a/firmware/target/arm/imx31/gigabeat-s/button-target.h +++ b/firmware/target/arm/imx31/gigabeat-s/button-target.h @@ -37,6 +37,8 @@ int button_read_device(void); void button_power_event(void); void headphone_detect_event(void); bool headphones_inserted(void); +void headphone_init(void); +void button_headphone_set(int button); /* Toshiba Gigabeat S-specific button codes */ @@ -55,9 +57,17 @@ bool headphones_inserted(void); #define BUTTON_NEXT (1 << 11) #define BUTTON_POWER (1 << 12) /* Read from PMIC */ -#define BUTTON_MAIN (0x1fff) +#define BUTTON_MAIN (0x00001fff) -#define BUTTON_REMOTE 0 +/* Remote control buttons */ +#define BUTTON_RC_VOL_UP (1 << 13) +#define BUTTON_RC_VOL_DOWN (1 << 14) +#define BUTTON_RC_FF (1 << 15) +#define BUTTON_RC_REW (1 << 16) +#define BUTTON_RC_PLAY (1 << 17) +#define BUTTON_RC_DSP (1 << 18) + +#define BUTTON_REMOTE (0x0007e000) #define POWEROFF_BUTTON BUTTON_POWER #define POWEROFF_COUNT 10 diff --git a/firmware/target/arm/imx31/gigabeat-s/headphone-gigabeat-s.c b/firmware/target/arm/imx31/gigabeat-s/headphone-gigabeat-s.c new file mode 100644 index 0000000000..6043d00bf5 --- /dev/null +++ b/firmware/target/arm/imx31/gigabeat-s/headphone-gigabeat-s.c @@ -0,0 +1,201 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (c) 2009 by Michael Sevakis + * + * Driver to handle headphone jack events + * + * 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. + * + ****************************************************************************/ +#include "config.h" +#include "system.h" +#include "kernel.h" +#include "thread.h" +#include "mc13783.h" +#include "mc13783-target.h" +#include "adc.h" +#include "button.h" + +static struct wakeup headphone_wakeup; +static unsigned int headphone_thread_id; +static int headphone_stack[160/sizeof(int)]; /* Not much stack needed */ +static const char * const headphone_thread_name = "headphone"; +static bool headphones_detect = false; + +/* Convert ADC reading into a button value. */ +static int adc_data_to_button(unsigned int data) +{ + int btn = BUTTON_NONE; + + if (data < 505) + { + if (data < 252) + { + if (data < 149) + { + if (data >= 64) + { + /* Play/Pause */ + btn = BUTTON_RC_PLAY; + } + /* else headphone direct */ + } + else + { + /* DSP */ + btn = BUTTON_RC_DSP; + } + } + else + { + if (data < 370) + { + /* RW */ + btn = BUTTON_RC_REW; + } + else + { + /* FF */ + btn = BUTTON_RC_FF; + } + } + } + else + { + if (data < 870) + { + if (data < 675) + { + /* Vol + */ + btn = BUTTON_RC_VOL_UP; + } + else + { + /* Vol - */ + btn = BUTTON_RC_VOL_DOWN; + } + } +#if 0 + else + { + + if (data < 951) + { + /* No buttons */ + } + else + { + /* Not inserted */ + + } + } +#endif + } + + return btn; +} + +static void headphone_thread(void) +{ + int headphone_sleep_countdown = 0; + int headphone_wait_timeout = TIMEOUT_BLOCK; + + while (1) + { + int rc = wakeup_wait(&headphone_wakeup, headphone_wait_timeout); + unsigned int data = adc_read(ADC_HPREMOTE); + + if (rc == OBJ_WAIT_TIMEDOUT) + { + if (headphone_sleep_countdown <= 0) + { + /* Polling ADC */ + int btn, btn2; + + btn = adc_data_to_button(data); + sleep(HZ/50); + data = adc_read(ADC_HPREMOTE); + btn2 = adc_data_to_button(data); + + if (btn != btn2) + { + /* If the buttons dont agree twice in a row, then it's + * none (from meg-fx remote reader). */ + btn = BUTTON_NONE; + } + + button_headphone_set(btn); + continue; + } + + if (--headphone_sleep_countdown == 0) + { + /* Nothing has changed and remote is not present - + * go to sleep. */ + headphone_wait_timeout = TIMEOUT_BLOCK; + continue; + } + } + + headphones_detect = data <= 951; /* Max remote value */ + + /* Cancel any buttons if jack readings are unstable. */ + button_headphone_set(BUTTON_NONE); + + if (data >= 64 && data <= 951) + { + /* Should be a remote control - accelerate */ + headphone_wait_timeout = HZ/20-HZ/50; + headphone_sleep_countdown = 0; + } + else if (rc == OBJ_WAIT_SUCCEEDED) + { + /* Got signaled - something is being plugged/unplugged. Set + * countdown until we just give up and go to sleep (~10s). */ + headphone_wait_timeout = HZ/2; + headphone_sleep_countdown = 10*2; + } + } +} + +/* This is called from the mc13783 interrupt thread */ +void headphone_detect_event(void) +{ + /* Trigger the thread immediately. */ + wakeup_signal(&headphone_wakeup); +} + +/* Tell if anything is in the jack. */ +bool headphones_inserted(void) +{ + return headphones_detect; +} + +void headphone_init(void) +{ + /* A thread is required to monitor the remote ADC and jack state. */ + wakeup_init(&headphone_wakeup); + headphone_thread_id = create_thread(headphone_thread, + headphone_stack, + sizeof(headphone_stack), + 0, headphone_thread_name + IF_PRIO(, PRIORITY_REALTIME) + IF_COP(, CPU)); + + /* Initially poll and then enable PMIC event */ + headphone_detect_event(); + mc13783_enable_event(MC13783_ONOFD2_EVENT); +}