From 0260852771aef7a6e9045684f0c3d0d7e01909f7 Mon Sep 17 00:00:00 2001 From: Laurent Gautier Date: Tue, 1 Dec 2009 17:54:40 +0000 Subject: [PATCH] Add support for the ipod FM remote to the 4G, Color, 5G, nano 1G with RDS git-svn-id: svn://svn.rockbox.org/rockbox/trunk@23805 a1c6a512-1295-4272-9138-f99709370657 --- apps/iap.c | 218 ++++++++-- apps/keymaps/keymap-ipod.c | 52 +++ apps/recorder/radio.c | 63 ++- docs/COMMITTERS | 1 + firmware/SOURCES | 5 + firmware/drivers/tuner/ipod_remote_tuner.c | 444 +++++++++++++++++++++ firmware/export/config-ipod4g.h | 6 +- firmware/export/config-ipodcolor.h | 6 +- firmware/export/config-ipodnano.h | 6 +- firmware/export/config-ipodvideo.h | 6 +- firmware/export/config.h | 1 + firmware/export/iap.h | 5 + firmware/export/ipod_remote_tuner.h | 75 ++++ firmware/export/tuner.h | 21 +- firmware/target/arm/audio-pp.c | 7 + firmware/tuner.c | 19 +- 16 files changed, 881 insertions(+), 54 deletions(-) create mode 100644 firmware/drivers/tuner/ipod_remote_tuner.c create mode 100644 firmware/export/ipod_remote_tuner.h diff --git a/apps/iap.c b/apps/iap.c index 29cee22ab5..741ff9fb0c 100644 --- a/apps/iap.c +++ b/apps/iap.c @@ -37,11 +37,12 @@ #include "settings.h" #include "metadata.h" #include "wps.h" - +#include "sound.h" #include "action.h" +#include "powermgmt.h" -#define RX_BUFLEN 260 -#define TX_BUFLEN 128 +#include "tuner.h" +#include "ipod_remote_tuner.h" static volatile int iap_pollspeed = 0; static volatile bool iap_remotetick = true; @@ -115,7 +116,7 @@ void iap_bitrate_set(int ratenum) checksum (length+mode+parameters+checksum == 0) */ -static void iap_send_pkt(const unsigned char * data, int len) +void iap_send_pkt(const unsigned char * data, int len) { int i, chksum; @@ -192,15 +193,15 @@ void iap_periodic(void) unsigned long time_elapsed = audio_current_track()->elapsed; time_elapsed += wps_get_ff_rewind_count(); - - data[3] = 0x04; // playing + + data[3] = 0x04; /* playing */ /* If info has changed, don't flag it right away */ if(iap_changedctr && iap_changedctr++ >= iap_pollspeed * 2) - { + { /* track info has changed */ iap_changedctr = 0; - data[3] = 0x01; // 0x02 has same effect? + data[3] = 0x01; /* 0x02 has same effect? */ iap_updateflag = true; } @@ -211,12 +212,19 @@ void iap_periodic(void) iap_send_pkt(data, sizeof(data)); } +void iap_set_remote_volume(void) +{ + unsigned char data[] = {0x03, 0x0D, 0x04, 0x00, 0x00}; + data[4] = (char)((global_settings.volume+58) * 4); + iap_send_pkt(data, sizeof(data)); +} + void iap_handlepkt(void) { - + if(!iap_setupflag) return; if(serbuf[0] == 0) return; - + /* if we are waiting for a remote button to go out, delay the handling of the new packet */ if(!iap_remotetick) @@ -224,63 +232,140 @@ void iap_handlepkt(void) queue_post(&button_queue, SYS_IAP_HANDLEPKT, 0); return; } - + /* Handle Mode 0 */ if (serbuf[1] == 0x00) { switch (serbuf[2]) { - /* get model info */ - case 0x0D: + case 0x24: { - unsigned char data[] = {0x00, 0x0E, 0x00, 0x0B, 0x00, 0x10, - 'R', 'O', 'C', 'K', 'B', 'O', 'X', 0x00}; + /* ipod video send this */ + unsigned char data[] = {0x00, 0x25, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00,0x01}; iap_send_pkt(data, sizeof(data)); break; } - /* No idea ??? */ + + case 0x18: + { + /* ciphered authentication command */ + /* Isn't used since we don't send the 0x00 0x17 command */ + break; + } + + case 0x15: + { + unsigned char data0[] = {0x00, 0x16, 0x00}; + iap_send_pkt(data0, sizeof(data0)); + unsigned char data1[] = {0x00, 0x27, 0x00}; + iap_send_pkt(data1, sizeof(data1)); + /* authentication ack, mandatory to enable some hardware */ + unsigned char data2[] = {0x00, 0x19, 0x00}; + iap_send_pkt(data2, sizeof(data2)); + if (radio_present == 1) + { + /* get tuner capacities */ + unsigned char data3[] = {0x07, 0x01}; + iap_send_pkt(data3, sizeof(data3)); + } + iap_set_remote_volume(); + break; + } + + case 0x13: + { + unsigned char data[] = {0x00, 0x02, 0x00, 0x13}; + iap_send_pkt(data, sizeof(data)); + + if (serbuf[6] == 0x35) + /* FM transmitter sends this: */ + /* FF 55 0E 00 13 00 00 00 35 00 00 00 04 00 00 00 00 A6 (??)*/ + { + unsigned char data2[] = {0x00, 0x27, 0x00}; + iap_send_pkt(data2, sizeof(data2)); + unsigned char data3[] = {0x05, 0x02}; + iap_send_pkt(data3, sizeof(data3)); + } + + else + { + /* ipod fm remote sends this: */ + /* FF 55 0E 00 13 00 00 00 8D 00 00 00 0E 00 00 00 03 41 */ + if (serbuf[6] |= 0x80) + radio_present = 1; + unsigned char data4[] = {0x00, 0x14}; + iap_send_pkt(data4, sizeof(data4)); + } + break; + } + + /* Init */ case 0x0F: { unsigned char data[] = {0x00, 0x10, 0x00, 0x01, 0x05}; + data[2] = serbuf[3]; iap_send_pkt(data, sizeof(data)); break; } - /* FM transmitter sends this: FF 55 06 00 01 05 00 02 01 F1 (mode switch) */ + + /* get model info */ + case 0x0D: + { + /* ipod is supposed to work only with 5G and nano 2G */ + /*{0x00, 0x0E, 0x00, 0x0B, 0x00, 0x05, 0x50, 0x41, 0x31, 0x34, + 0x37, 0x4C, 0x4C, 0x00}; PA147LL (IPOD 5G 60 GO) */ + unsigned char data[] = {0x00, 0x0E, 0x00, 0x0B, 0x00, 0x10, + 'R', 'O', 'C', 'K', 'B', 'O', 'X', 0x00}; + iap_send_pkt(data, sizeof(data)); + break; + } + + /* Ipod FM remote sends this: FF 55 02 00 09 F5 */ + case 0x09: + { + /* ipod5G firmware version */ + unsigned char data[] = {0x00, 0x0A, 0x01, 0x02, 0x01 }; + iap_send_pkt(data, sizeof(data)); + break; + } + + /* FM transmitter sends this: */ + /* FF 55 02 00 05 F9 (mode switch: AiR mode) */ + case 0x05: + { + unsigned char data[] = {0x00, 0x02, 0x06, + 0x05, 0x00, 0x00, 0x0B, 0xB8, 0x28}; + iap_send_pkt(data, sizeof(data)); + unsigned char data2[] = {0x00, 0x02, 0x00, 0x05}; + iap_send_pkt(data2, sizeof(data2)); + break; + } + case 0x01: { - if(serbuf[3] == 0x05) + /* FM transmitter sends this: */ + /* FF 55 06 00 01 05 00 02 01 F1 (mode switch) */ + if(serbuf[3] == 0x05) { sleep(HZ/3); unsigned char data[] = {0x05, 0x02}; iap_send_pkt(data, sizeof(data)); } + /* FM remote sends this: */ + /* FF 55 03 00 01 02 FA (1st thing sent) */ + else if(serbuf[3] == 0x02) + { + /* useful only for apple firmware */ + } break; } - /* FM transmitter sends this: FF 55 0E 00 13 00 00 00 35 00 00 00 04 00 00 00 00 A6 (???)*/ - case 0x13: - { - unsigned char data[] = {0x00, 0x02, 0x00, 0x13}; - iap_send_pkt(data, sizeof(data)); - unsigned char data2[] = {0x00, 0x27, 0x00}; - iap_send_pkt(data2, sizeof(data2)); - unsigned char data3[] = {0x05, 0x02}; - iap_send_pkt(data3, sizeof(data3)); - break; - } - /* FM transmitter sends this: FF 55 02 00 05 F9 (mode switch: AiR mode) */ - case 0x05: - { - unsigned char data[] = {0x00, 0x02, 0x06, 0x05, 0x00, 0x00, 0x0B, 0xB8, 0x28}; - iap_send_pkt(data, sizeof(data)); - unsigned char data2[] = {0x00, 0x02, 0x00, 0x05}; - iap_send_pkt(data2, sizeof(data2)); - break; - } + /* default response is with cmd ok packet */ default: { unsigned char data[] = {0x00, 0x02, 0x00, 0x00}; - data[3] = serbuf[2]; //respond with cmd + data[3] = serbuf[2]; /* respond with cmd */ iap_send_pkt(data, sizeof(data)); break; } @@ -395,6 +480,30 @@ void iap_handlepkt(void) iap_send_pkt(data, sizeof(data)); break; } + + case 0x08: + { + /* ACK */ + unsigned char data[] = {0x03, 0x00, 0x00, 0x08}; + iap_send_pkt(data, sizeof(data)); + break; + } + + case 0x0C: + { + /* request ipod volume */ + if (serbuf[3] == 0x04) + { + iap_set_remote_volume(); + } + break; + } + /* get volume from accessory */ + case 0x0E: + if (serbuf[3] == 0x04) + global_settings.volume = (-58)+((int)serbuf[5]+1)/4; + sound_set_volume(global_settings.volume); + break; } } /* Handle Mode 4 */ @@ -712,6 +821,37 @@ void iap_handlepkt(void) } } } + /* Handle Mode 7 */ + else if (serbuf[1] == 0x07) + { + switch(serbuf[2]) + { + /* tuner capabilities */ + case 0x02: + { + /* do nothing */ + + unsigned char data[] = {0x00, 0x27, 0x00}; + iap_send_pkt(data, sizeof(data)); + break; + } + /* actual tuner frequency */ + case 0x0A: + /* fall through */ + /* tuner frequency from scan */ + case 0x13: + { + rmt_tuner_freq(); + break; + } + /* RDS station name 0x21 1E 00 + ASCII text*/ + case 0x21: + { + rmt_tuner_rds_data(); + break; + } + } + } serbuf[0] = 0; } diff --git a/apps/keymaps/keymap-ipod.c b/apps/keymaps/keymap-ipod.c index 1a8f7a25ff..9b3323bb82 100644 --- a/apps/keymaps/keymap-ipod.c +++ b/apps/keymaps/keymap-ipod.c @@ -193,6 +193,26 @@ const struct button_mapping button_context_recscreen[] = { }; /* button_context_recscreen */ #endif +/** FM Radio Screen **/ + #if CONFIG_TUNER + static const struct button_mapping button_context_radio[] = { + { ACTION_FM_MENU, BUTTON_SELECT | BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_FM_STOP, BUTTON_PLAY | BUTTON_REPEAT, BUTTON_PLAY }, + { ACTION_FM_MODE, BUTTON_SELECT, BUTTON_NONE }, + { ACTION_FM_EXIT, BUTTON_MENU | BUTTON_REL, BUTTON_NONE }, + { ACTION_FM_PLAY, BUTTON_PLAY | BUTTON_REL, BUTTON_PLAY }, + { ACTION_SETTINGS_INC, BUTTON_SCROLL_FWD, BUTTON_NONE }, + { ACTION_SETTINGS_INCREPEAT,BUTTON_SCROLL_FWD|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_SETTINGS_DEC, BUTTON_SCROLL_BACK, BUTTON_NONE }, + { ACTION_SETTINGS_DECREPEAT,BUTTON_SCROLL_BACK|BUTTON_REPEAT,BUTTON_NONE }, + { ACTION_STD_NEXT, BUTTON_RIGHT, BUTTON_NONE }, + { ACTION_STD_NEXTREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_STD_PREV, BUTTON_LEFT, BUTTON_NONE }, + { ACTION_STD_PREVREPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_SETTINGS) + }; /* button_context_radio */ + #endif + #ifdef USB_ENABLE_HID static const struct button_mapping button_context_usb_hid[] = { { ACTION_USB_HID_MODE_SWITCH_NEXT, BUTTON_SELECT|BUTTON_RIGHT|BUTTON_REL, BUTTON_SELECT|BUTTON_RIGHT }, @@ -311,6 +331,26 @@ static const struct button_mapping remote_button_context_wps[] = { LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) }; /* remote_button_context_wps */ +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 }, + + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) +}; /* remote_button_context_tree */ + +#if CONFIG_TUNER + static const struct button_mapping remote_button_context_radio[] = { + { ACTION_FM_STOP, BUTTON_RC_PLAY | BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_FM_PLAY, BUTTON_RC_PLAY | BUTTON_REL, BUTTON_RC_PLAY }, + { ACTION_STD_NEXT, BUTTON_RC_RIGHT|BUTTON_REL, BUTTON_RC_RIGHT }, + { ACTION_STD_NEXTREPEAT, BUTTON_RC_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_STD_PREV, BUTTON_RC_LEFT|BUTTON_REL, BUTTON_RC_LEFT }, + { ACTION_STD_PREVREPEAT, BUTTON_RC_LEFT|BUTTON_REPEAT, BUTTON_NONE }, + + LAST_ITEM_IN_LIST + }; /* remote_button_context_radio */ +#endif + static const struct button_mapping* get_context_mapping_remote( int context ) { context ^= CONTEXT_REMOTE; @@ -319,6 +359,14 @@ static const struct button_mapping* get_context_mapping_remote( int context ) { case CONTEXT_WPS: return remote_button_context_wps; + case CONTEXT_TREE: + case CONTEXT_CUSTOM|CONTEXT_TREE: + return remote_button_context_tree; + +#ifdef CONFIG_TUNER + case CONTEXT_FM: + return remote_button_context_radio; +#endif default: return remote_button_context_standard; } @@ -371,6 +419,10 @@ const struct button_mapping* get_context_mapping(int context) case CONTEXT_RECSCREEN: return button_context_recscreen; #endif +#if CONFIG_TUNER + case CONTEXT_FM: + return button_context_radio; +#endif #ifdef USB_ENABLE_HID case CONTEXT_USB_HID: return button_context_usb_hid; diff --git a/apps/recorder/radio.c b/apps/recorder/radio.c index b70c682922..87614aec15 100644 --- a/apps/recorder/radio.c +++ b/apps/recorder/radio.c @@ -49,6 +49,9 @@ #ifdef HAVE_RECORDING #include "recording.h" #endif +#ifdef IPOD_ACCESSORY_PROTOCOL +#include "iap.h" +#endif #include "talk.h" #include "tuner.h" #include "power.h" @@ -114,6 +117,15 @@ #define FM_MODE #define FM_EXIT #define FM_PLAY + +#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \ + (CONFIG_KEYPAD == IPOD_1G2G_PAD) +#define FM_MENU +#define FM_STOP +#define FM_EXIT +#define FM_PLAY +#define FM_MODE + #endif #define RADIO_SCAN_MODE 0 @@ -586,7 +598,6 @@ int radio_screen(void) end_search(); talk = true; } - trigger_cpu_boost(); } @@ -873,6 +884,33 @@ int radio_screen(void) default: default_event_handler(button); +#ifdef HAVE_RDS_CAP + if (tuner_get(RADIO_EVENT)) + update_screen = true; +#endif + if (!tuner_get(RADIO_PRESENT)) + { +#if CONFIG_CODEC != SWCODEC && !defined(SIMULATOR) + if(audio_status() == AUDIO_STATUS_RECORD) + audio_stop(); +#endif + keep_playing = false; + done = true; + ret_val = GO_TO_ROOT; + if(presets_changed) + { + if(yesno_pop(ID2P(LANG_FM_SAVE_CHANGES))) + { + if(filepreset[0] == '\0') + save_preset_list(); + else + radio_save_presets(); + } + } + + /* Clear the preset list on exit. */ + clear_preset_list(); + } break; } /*switch(button)*/ @@ -895,11 +933,11 @@ int radio_screen(void) { screens[i].set_viewport(&vp[i]); peak_meter_screen(&screens[i],0, - STATUSBAR_HEIGHT + fh*(top_of_screen + 4), - fh); + STATUSBAR_HEIGHT + fh*(top_of_screen + 4), + fh); screens[i].update_rect(0, - STATUSBAR_HEIGHT + fh*(top_of_screen + 4), - screens[i].getwidth(), fh); + STATUSBAR_HEIGHT + fh*(top_of_screen + 4), + screens[i].getwidth(), fh); screens[i].set_viewport(NULL); } } @@ -963,7 +1001,18 @@ int radio_screen(void) str(LANG_RADIO_SCAN_MODE)); FOR_NB_SCREENS(i) screens[i].puts_scroll(0, top_of_screen + 3, buf); +#ifndef SIMULATOR +#ifdef HAVE_RDS_CAP + snprintf(buf, 128, "%s",tuner_get_rds_info(RADIO_RDS_NAME)); + FOR_NB_SCREENS(i) + screens[i].puts_scroll(0, top_of_screen + 4, buf); + snprintf(buf, 128, "%s",tuner_get_rds_info(RADIO_RDS_TEXT)); + FOR_NB_SCREENS(i) + screens[i].puts_scroll(0, top_of_screen + 5, buf); +#endif +#endif /* SIMULATOR */ + #if CONFIG_CODEC != SWCODEC if(audio_status() == AUDIO_STATUS_RECORD) { @@ -1498,6 +1547,7 @@ static int scan_presets(void *viewports) curr_freq = fmr->freq_min; num_presets = 0; memset(presets, 0, sizeof(presets)); + tuner_set(RADIO_MUTE, 1); while(curr_freq <= fmr->freq_max) @@ -1563,7 +1613,6 @@ static int fm_recording_screen(void) /* switch recording source to FMRADIO for the duration */ int rec_source = global_settings.rec_source; global_settings.rec_source = AUDIO_SRC_FMRADIO; - ret = recording_screen(true); /* safe to reset as changing sources is prohibited here */ @@ -1649,7 +1698,7 @@ MAKE_MENU(radio_settings_menu, ID2P(LANG_FM_MENU), NULL, static bool radio_menu(void) { return do_menu(&radio_settings_menu, NULL, NULL, false) == - MENU_ATTACHED_USB; + MENU_ATTACHED_USB; } #endif diff --git a/docs/COMMITTERS b/docs/COMMITTERS index 0b107e1c0a..705891a543 100644 --- a/docs/COMMITTERS +++ b/docs/COMMITTERS @@ -61,6 +61,7 @@ kjer Kjell Ericson kkurbjun Karl Kurbjun kugel Thomas Martitz lamed Shachar Liberman +laurent Gautier learman Magnus Holmgren len0x Anton Oleynikov lenzone10 Alessio Lenzi diff --git a/firmware/SOURCES b/firmware/SOURCES index 061c6323c8..1a078ada29 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -201,6 +201,7 @@ drivers/rtc/rtc_d2.c #endif /* (CONFIG_RTC == RTC_) */ #endif /* SIMULATOR */ +#ifndef BOOTLOADER /* Tuner */ #if CONFIG_TUNER tuner.c @@ -221,8 +222,12 @@ drivers/tuner/tea5767.c #if (CONFIG_TUNER & SI4700) drivers/tuner/si4700.c #endif /* (CONFIG_TUNER & SI4700) */ +#if (CONFIG_TUNER & IPOD_REMOTE_TUNER) +drivers/tuner/ipod_remote_tuner.c +#endif /* (CONFIG_TUNER & IPOD_REMOTE_TUNER) */ #endif /*SIMULATOR */ #endif /* CONFIG_TUNER */ +#endif /* BOOTLOADER */ /* Sound */ #if CONFIG_CODEC != SWCODEC diff --git a/firmware/drivers/tuner/ipod_remote_tuner.c b/firmware/drivers/tuner/ipod_remote_tuner.c new file mode 100644 index 0000000000..07a5eeb9a6 --- /dev/null +++ b/firmware/drivers/tuner/ipod_remote_tuner.c @@ -0,0 +1,444 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * tuner for the ipod fm remote and other ipod remote tuners + * + * Copyright (C) 2009 Laurent Gautier + * + * 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 +#include +#include +#include +#include "kernel.h" +#include "iap.h" +#include "tuner.h" /* tuner abstraction interface */ +#include "adc.h" +#include "settings.h" + +static bool powered = false; + +static unsigned char tuner_param = 0x00, old_tuner_param = 0xFF; +/* temp var for tests to avoid looping execution in submenus settings*/ +int mono_mode = -1, old_region = -1; + +int radio_present = 0; +int tuner_frequency = 0; +int tuner_signal_power = 0; +int radio_tuned = 0; +int rds_event = 0; + +char rds_radioname[9]; +char rds_radioinfo[70]; /* do we need more? */ + +union FRQ { + unsigned long int frequency_radio; + char data_frequency[4]; +}Frequency; + +void rmt_tuner_freq(void) +{ + char tempdata[4]; + tempdata[0] = serbuf[6]; + tempdata[1] = serbuf[5]; + tempdata[2] = serbuf[4]; + tempdata[3] = serbuf[3]; + + memcpy(Frequency.data_frequency,tempdata,4); + tuner_frequency = (Frequency.frequency_radio*1000); + radio_tuned = 1; + rmt_tuner_signal_power(serbuf[7]); +} + +void rmt_tuner_set_freq(int curr_freq) +{ + if (curr_freq != tuner_frequency) + { + radio_tuned = 0; + tuner_signal_power = 0; + /* clear rds name and info */ + memset(rds_radioname,' ',sizeof(rds_radioname)); + memset(rds_radioinfo,' ',sizeof(rds_radioinfo)); + /* ex: 00 01 63 14 = 90.9MHz */ + unsigned char data[] = {0x07, 0x0B, 0x00, 0x01, 0x63, 0x14}; + + if (curr_freq != 0) + { + curr_freq = curr_freq / 1000; + char tempdata[4]; + + Frequency.frequency_radio = curr_freq; + tempdata[0] = Frequency.data_frequency[3]; + tempdata[1] = Frequency.data_frequency[2]; + tempdata[2] = Frequency.data_frequency[1]; + tempdata[3] = Frequency.data_frequency[0]; + + memcpy(data+2,tempdata,4); + iap_send_pkt(data, sizeof(data)); + } + } +} + +void rmt_tuner_signal_power(unsigned char value) +{ + tuner_signal_power = (int)(value); +} + +void rmt_tuner_sleep(int state) +{ + if (state == 0) + { + /* tuner HW on */ + unsigned char data[] = {0x07, 0x05, 0x01}; + iap_send_pkt(data, sizeof(data)); + /* boost gain */ + unsigned char data1[] = {0x07, 0x24, 0x06 }; + iap_send_pkt(data1, sizeof(data1)); + /* set volume */ + unsigned char data2[] = {0x03, 0x09, 0x04, 0x00, 0x77 }; + iap_send_pkt(data2, sizeof(data2)); + /* set rds on */ + unsigned char data3[] = {0x07, 0x20, 0x40, 0x00, 0x00, 0x10 }; + iap_send_pkt(data3, sizeof(data3)); + } + else + { + /* unbooste gain */ + unsigned char data[] = {0x07, 0x24, 0x00}; + iap_send_pkt(data, sizeof(data)); + /* set rds off */ + unsigned char data1[] = {0x07, 0x20, 0x00, 0x00, 0x00, 0x00 }; + iap_send_pkt(data1, sizeof(data1)); + /* stop tuner HW */ + unsigned char data2[] = {0x07, 0x05, 0x00}; + iap_send_pkt(data2, sizeof(data2)); + } +} + +void rmt_tuner_scan(int param) +{ + unsigned char data[] = {0x07, 0x11, 0x08}; /* RSSI level */ + unsigned char updown = 0x00; + radio_tuned = 0; + iap_send_pkt(data, sizeof(data)); + + if (param == 1) + { + updown = 0x07; /* scan up */ + } + else if (param == -1) + { + updown = 0x08; /* scan down */ + } + else if (param == 10) + { + updown = 0x01; /* scan up starting from beginning of the band */ + } + unsigned char data1[] = {0x07, 0x12, updown}; + iap_send_pkt(data1, sizeof(data1)); +} + +void rmt_tuner_mute(int value) +{ + /* mute flag off (play) */ + unsigned char data[] = {0x03, 0x09, 0x03, 0x01}; + if (value) + { + /* mute flag on (pause) */ + data[3] = 0x02; + } + iap_send_pkt(data, sizeof(data)); +} + +void rmt_tuner_region(int region) +{ + if (region != old_region) + { + unsigned char data[] = {0x07, 0x08, 0x00}; + if (region == 2) + { + data[2] = 0x02; /* japan band */ + } + else + { + data[2] = 0x01; /* us eur band */ + } + iap_send_pkt(data, sizeof(data)); + sleep(HZ/100); + old_region = region; + } +} + +/* set stereo/mono, deemphasis, delta freq... */ +void rmt_tuner_set_param(unsigned char tuner_param) +{ + if(tuner_param != old_tuner_param) + { + unsigned char data[] = {0x07, 0x0E, 0x00}; + + data[2] = tuner_param; + iap_send_pkt(data, sizeof(data)); + old_tuner_param = tuner_param; + } +} + +void set_deltafreq(int delta) +{ + tuner_param &= 0xFC; + switch (delta) + { + case 1: + { + /* 100KHz */ + tuner_param |= 0x01; + break; + } + case 2: + { + /* 50KHz */ + tuner_param |= 0x02; + break; + } + + default: + { + /* 200KHz */ + tuner_param |= 0x00; + break; + } + } +} + +void set_deemphasis(int deemphasis) +{ + tuner_param &= 0xBF; + switch (deemphasis) + { + case 1: + { + tuner_param |= 0x40; + break; + } + default: + { + tuner_param |= 0x00; + break; + } + } +} + +void set_mono(int value) +{ + tuner_param &= 0xEF; + + if (value != mono_mode) + { + tuner_param |= 0x10; + rmt_tuner_set_param(tuner_param); + sleep(HZ/100); + mono_mode = value; + } +} + +bool reply_timeout(void) +{ + int timeout = 0; + + sleep(HZ/50); + do + { + iap_handlepkt(); + sleep(HZ/50); + timeout++; + } + while((ipod_rmt_tuner_get(RADIO_TUNED) == 0) && (timeout < TIMEOUT_VALUE)); + + if (timeout >= TIMEOUT_VALUE) + return true; + else return false; +} + +void rmt_tuner_rds_data(void) +{ + if (serbuf[3] == 0x1E) + { + strlcpy(rds_radioname,serbuf+5,8); + } + else if(serbuf[3] == 0x04) + { + strlcpy(rds_radioinfo,serbuf+5,(serbuf[0]-4)); + } + rds_event = 1; +} + +/* tuner abstraction layer: set something to the tuner */ +int ipod_rmt_tuner_set(int setting, int value) +{ + switch(setting) + { + case RADIO_SLEEP: + { + rmt_tuner_sleep(value); + sleep(HZ/2); + break; + } + + case RADIO_FREQUENCY: + { + rmt_tuner_set_freq(value); + if (reply_timeout() == true) + return 0; + break; + } + + case RADIO_SCAN_FREQUENCY: + { + const struct fm_region_data * const fmr = + &fm_region_data[global_settings.fm_region]; + + /* case: scan for presets, back to beginning of the band */ + if ((radio_tuned == 1) && (value == fmr->freq_min)) + { + tuner_set(RADIO_FREQUENCY,value); + } + + /* scan through frequencies */ + if (radio_tuned == 1) + { + /* scan down */ + if(value < tuner_frequency) + rmt_tuner_scan(-1); + /* scan up */ + else + rmt_tuner_scan(1); + + if (reply_timeout() == true) + return 0; + radio_tuned = 0; + } + + if (tuner_frequency == value) + { + radio_tuned = 1; + return 1; + } + else + { + radio_tuned = 0; + return 0; + } + } + + case RADIO_MUTE: + { + /* mute flag sent to accessory */ + /* rmt_tuner_mute(value); */ + break; + } + + case RADIO_REGION: + { + const struct rmt_tuner_region_data *rd = + &rmt_tuner_region_data[value]; + + rmt_tuner_region(rd->band); + set_deltafreq(rd->spacing); + set_deemphasis(rd->deemphasis); + rmt_tuner_set_param(tuner_param); + break; + } + + case RADIO_FORCE_MONO: + { + set_mono(value); + break; + } + + default: + return -1; + } + return 1; +} + +/* tuner abstraction layer: read something from the tuner */ +int ipod_rmt_tuner_get(int setting) +{ + int val = -1; /* default for unsupported query */ + + switch(setting) + { + case RADIO_PRESENT: + val = radio_present; + if (val) + { + /* if accessory disconnected */ + if(adc_read(ADC_ACCESSORY) >= 10) + { + radio_present = 0; + val = 0; + } + } + break; + + /* radio tuned: yes no */ + case RADIO_TUNED: + val = 0; + if (radio_tuned == 1) + val = 1; + break; + + /* radio is always stereo */ + /* we can't know when it's in mono mode, depending of signal quality */ + /* except if it is forced in mono mode */ + case RADIO_STEREO: + val = true; + break; + + case RADIO_EVENT: + if (rds_event) + { + val = 1; + rds_event = 0; + } + break; + } + return val; +} + +char* ipod_get_rds_info(int setting) +{ + char *text = NULL; + + switch(setting) + { + case RADIO_RDS_NAME: + text = rds_radioname; + break; + + case RADIO_RDS_TEXT: + text = rds_radioinfo; + break; + } + return text; +} + +bool tuner_power(bool status) +{ + bool oldstatus = powered; + powered = status; + return oldstatus; +} diff --git a/firmware/export/config-ipod4g.h b/firmware/export/config-ipod4g.h index c00a6788c6..e6bdc35bd8 100644 --- a/firmware/export/config-ipod4g.h +++ b/firmware/export/config-ipod4g.h @@ -18,7 +18,7 @@ /* Define bitmask of input sources - recordable bitmask can be defined explicitly if different */ -#define INPUT_SRC_CAPS (SRC_CAP_MIC | SRC_CAP_LINEIN) +#define INPUT_SRC_CAPS (SRC_CAP_MIC | SRC_CAP_LINEIN | SRC_CAP_FMRADIO) /* define the bitmask of hardware sample rates */ #define HW_SAMPR_CAPS (SAMPR_CAP_44) @@ -144,6 +144,10 @@ #define CURRENT_RECORD 35 /* FIXME: this needs adjusting */ #endif +/* Define Apple remote tuner */ +#define CONFIG_TUNER IPOD_REMOTE_TUNER +#define HAVE_RDS_CAP + /* Define this if you have a PortalPlayer PP5020 */ #define CONFIG_CPU PP5020 diff --git a/firmware/export/config-ipodcolor.h b/firmware/export/config-ipodcolor.h index 751522d47b..0f1de4fdba 100644 --- a/firmware/export/config-ipodcolor.h +++ b/firmware/export/config-ipodcolor.h @@ -18,7 +18,7 @@ /* Define bitmask of input sources - recordable bitmask can be defined explicitly if different */ -#define INPUT_SRC_CAPS (SRC_CAP_MIC | SRC_CAP_LINEIN) +#define INPUT_SRC_CAPS (SRC_CAP_MIC | SRC_CAP_LINEIN | SRC_CAP_FMRADIO) /* define the bitmask of hardware sample rates */ #define HW_SAMPR_CAPS (SAMPR_CAP_44) @@ -121,6 +121,10 @@ /* define this if the unit can be powered or charged via USB */ #define HAVE_USB_POWER +/* Define Apple remote tuner */ +#define CONFIG_TUNER IPOD_REMOTE_TUNER +#define HAVE_RDS_CAP + /* Define this if you have a PortalPlayer PP5020 */ #define CONFIG_CPU PP5020 diff --git a/firmware/export/config-ipodnano.h b/firmware/export/config-ipodnano.h index 68ed152cda..e1ec1afa86 100644 --- a/firmware/export/config-ipodnano.h +++ b/firmware/export/config-ipodnano.h @@ -18,7 +18,7 @@ /* Define bitmask of input sources - recordable bitmask can be defined explicitly if different */ -#define INPUT_SRC_CAPS (SRC_CAP_MIC | SRC_CAP_LINEIN) +#define INPUT_SRC_CAPS (SRC_CAP_MIC | SRC_CAP_LINEIN | SRC_CAP_FMRADIO) /* define the bitmask of hardware sample rates */ #define HW_SAMPR_CAPS (SAMPR_CAP_44) @@ -134,6 +134,10 @@ #define CURRENT_RECORD 35 /* FIXME: this needs adjusting */ #endif +/* Define Apple remote tuner */ +#define CONFIG_TUNER IPOD_REMOTE_TUNER +#define HAVE_RDS_CAP + /* Define this if you have a PortalPlayer PP5022 */ #define CONFIG_CPU PP5022 diff --git a/firmware/export/config-ipodvideo.h b/firmware/export/config-ipodvideo.h index f00de9cbfd..59ac6466e5 100644 --- a/firmware/export/config-ipodvideo.h +++ b/firmware/export/config-ipodvideo.h @@ -18,7 +18,7 @@ /* Define bitmask of input sources - recordable bitmask can be defined explicitly if different */ -#define INPUT_SRC_CAPS (SRC_CAP_MIC | SRC_CAP_LINEIN) +#define INPUT_SRC_CAPS (SRC_CAP_MIC | SRC_CAP_LINEIN | SRC_CAP_FMRADIO) /* define the bitmask of hardware sample rates */ #define HW_SAMPR_CAPS (SAMPR_CAP_48 | SAMPR_CAP_44 | SAMPR_CAP_32 | \ @@ -155,6 +155,10 @@ #define CURRENT_RECORD 35 /* FIXME: this needs adjusting */ #endif +/* Define Apple remote tuner */ +#define CONFIG_TUNER IPOD_REMOTE_TUNER +#define HAVE_RDS_CAP + /* Define this if you have a PortalPlayer PP5022 */ #define CONFIG_CPU PP5022 diff --git a/firmware/export/config.h b/firmware/export/config.h index 9c7beca2bd..088cab7212 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -40,6 +40,7 @@ #define SI4700 0x08 /* Silicon Labs */ #define TEA5760 0x10 /* Philips */ #define LV240000 0x20 /* Sanyo */ +#define IPOD_REMOTE_TUNER 0x40 /* Apple */ /* CONFIG_CODEC */ #define MAS3587F 3587 diff --git a/firmware/export/iap.h b/firmware/export/iap.h index e945d0c979..d00e5f398a 100644 --- a/firmware/export/iap.h +++ b/firmware/export/iap.h @@ -20,6 +20,9 @@ #ifndef __IAP_H__ #define __IAP_H__ +#define RX_BUFLEN 260 +#define TX_BUFLEN 128 + extern int iap_getc(unsigned char x); extern void iap_write_pkt(unsigned char data, int len); extern void iap_setup(int ratenum); @@ -27,5 +30,7 @@ extern void iap_bitrate_set(int ratenum); extern void iap_periodic(void); extern void iap_handlepkt(void); extern void iap_track_changed(void *ignored); +extern void iap_send_pkt(const unsigned char * data, int len); +extern unsigned char serbuf[RX_BUFLEN]; #endif diff --git a/firmware/export/ipod_remote_tuner.h b/firmware/export/ipod_remote_tuner.h new file mode 100644 index 0000000000..28fcfe1e93 --- /dev/null +++ b/firmware/export/ipod_remote_tuner.h @@ -0,0 +1,75 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id: ipod_remote_tuner.h + * Tuner header for the ipod remote tuner and others remote tuners + * + * Copyright (C) 2009 Laurent Gautier + * + * 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. + * + ****************************************************************************/ + +#ifndef _IPOD_REMOTE_TUNER_H_ +#define _IPOD_REMOTE_TUNER_H_ + +#define HAVE_RADIO_REGION +#define TIMEOUT_VALUE 20 + +extern int radio_present; +extern int tuner_frequency; +extern int tuner_signal_power; +extern int radio_tuned; + +/* update tuner state: plugged or unplugged when in radio mode */ +extern void rmt_tuner_region(int region); +extern void rmt_tuner_set_freq(int curr_freq); +extern void rmt_tuner_freq(void); +extern void rmt_tuner_scan(int direction); + +/* tuner mode state: ON or OFF */ +extern void rmt_tuner_sleep(int state); + +/* parameters are stereo/mono, deemphasis, delta freq... */ +extern void rmt_tuner_set_param(unsigned char tuner_param); + +extern void rmt_tuner_mute(int value); +extern void rmt_tuner_signal_power(unsigned char value); + +extern void rmt_tuner_rds_data(void); + +struct rmt_tuner_region_data +{ + /* 0: 50us, 1: 75us */ + unsigned char deemphasis; + /* 0: europe, 1: japan (BL in TEA spec)*/ + unsigned char band; + /* 0: us/australia (200kHz), 1: europe/japan (100kHz), 2: (50kHz) */ + unsigned char spacing; +} __attribute__((packed)); + +extern const struct rmt_tuner_region_data + rmt_tuner_region_data[TUNER_NUM_REGIONS]; + +int ipod_rmt_tuner_set(int setting, int value); +int ipod_rmt_tuner_get(int setting); +char* ipod_get_rds_info(int setting); + + +#ifndef CONFIG_TUNER_MULTI +#define tuner_set ipod_rmt_tuner_set +#define tuner_get ipod_rmt_tuner_get +#define tuner_get_rds_info ipod_get_rds_info +#endif + +#endif /* _IPOD_REMOTE_TUNER_H_ */ diff --git a/firmware/export/tuner.h b/firmware/export/tuner.h index b27e660667..63392160e5 100644 --- a/firmware/export/tuner.h +++ b/firmware/export/tuner.h @@ -33,7 +33,7 @@ enum RADIO_MUTE, RADIO_FORCE_MONO, RADIO_SCAN_FREQUENCY, - + /* Put new general-purpose settings above this line */ __RADIO_SET_STANDARD_LAST }; @@ -44,11 +44,25 @@ enum RADIO_PRESENT = 0, RADIO_TUNED, RADIO_STEREO, + /* RADIO_EVENT is an event that requests a screen update */ + RADIO_EVENT, /* Put new general-purpose readback values above this line */ __RADIO_GET_STANDARD_LAST }; +#ifdef HAVE_RDS_CAP +/** Readback from the tuner RDS layer **/ +enum +{ + RADIO_RDS_NAME, + RADIO_RDS_TEXT, + + /* Put new general-purpose readback values above this line */ + __RADIO_GET_RDS_INFO_STANDARD_LAST +}; +#endif + /** Tuner regions **/ /* Basic region information */ @@ -114,6 +128,11 @@ extern int (*tuner_get)(int setting); #include "si4700.h" #endif +/* Apple remote tuner */ +#if (CONFIG_TUNER & IPOD_REMOTE_TUNER) +#include "ipod_remote_tuner.h" +#endif + #endif /* SIMULATOR */ /* Additional messages that get enumerated after tuner driver headers */ diff --git a/firmware/target/arm/audio-pp.c b/firmware/target/arm/audio-pp.c index 1a4952fcc3..8f22bab765 100644 --- a/firmware/target/arm/audio-pp.c +++ b/firmware/target/arm/audio-pp.c @@ -104,11 +104,17 @@ void audio_input_mux(int source, unsigned flags) if (!recording) audiohw_set_recvol(0x17, 0x17, AUDIO_GAIN_LINEIN); #endif + if (source == last_source && recording == last_recording) break; last_recording = recording; +#if defined(IPOD_REMOTE_TUNER) + /* Ipod FM tuner is in the remote connected to line-in */ + audiohw_enable_recording(false); /* source line */ + audiohw_set_monitor(true); /* enable bypass mode */ +#else if (recording) { audiohw_set_monitor(false); /* disable bypass mode */ @@ -119,6 +125,7 @@ void audio_input_mux(int source, unsigned flags) audiohw_disable_recording(); audiohw_set_monitor(true); /* enable bypass mode */ } +#endif break; #endif } /* end switch */ diff --git a/firmware/tuner.c b/firmware/tuner.c index 5fd7fa1f38..cca5cf2491 100644 --- a/firmware/tuner.c +++ b/firmware/tuner.c @@ -89,17 +89,31 @@ const struct si4700_region_data si4700_region_data[TUNER_NUM_REGIONS] = }; #endif /* (CONFIG_TUNER & SI4700) */ +#if (CONFIG_TUNER & IPOD_REMOTE_TUNER) +const struct rmt_tuner_region_data + rmt_tuner_region_data[TUNER_NUM_REGIONS] = +{ + [REGION_EUROPE] = { 1, 0, 1 }, /* 50uS, US/Europe band, 100kHz spacing */ + [REGION_US_CANADA] = { 0, 0, 0 }, /* 75uS, US/Europe band, 200kHz spacing */ + [REGION_JAPAN] = { 1, 2, 1 }, /* 50uS, Japanese band, 100kHz spacing */ + [REGION_KOREA] = { 1, 0, 0 }, /* 50uS, US/Europe band, 200kHz spacing */ + [REGION_ITALY] = { 1, 0, 2 }, /* 50uS, US/Europe band, 50kHz spacing */ + [REGION_OTHER] = { 1, 0, 2 }, /* 50uS, US/Europe band, 50kHz spacing */ +}; +#endif /* (CONFIG_TUNER & IPOD_REMOTE_TUNER) */ + #ifdef CONFIG_TUNER_MULTI int (*tuner_set)(int setting, int value); int (*tuner_get)(int setting); -#define TUNER_TYPE_CASE(type, set, get, ...) \ + +#define TUNER_TYPE_CASE(type, set, get, ...) \ case type: \ tuner_set = set; \ tuner_get = get; \ __VA_ARGS__; \ break; #else -#define TUNER_TYPE_CASE(type, set, get, ...) \ +#define TUNER_TYPE_CASE(type, set, get, ...) \ __VA_ARGS__; #endif /* CONFIG_TUNER_MULTI */ @@ -139,5 +153,4 @@ void tuner_init(void) #endif } } - #endif /* SIMULATOR */