From 21a4a87ca2553834b3f7b0c8f95f1b0889d8cb2c Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Wed, 6 Jun 2007 19:23:48 +0000 Subject: Accept FS#7178 - Sansa e200 FM tuner support by Ivan Zupan. Do the needed integration work into recording and the AS3514 audio driver. Do a little AS3514 fiq_record tweak to have it all work nicely from the start. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13573 a1c6a512-1295-4272-9138-f99709370657 --- apps/debug_menu.c | 34 +- apps/keymaps/keymap-e200.c | 15 +- apps/recorder/radio.c | 34 +- docs/CREDITS | 1 + firmware/SOURCES | 3 + firmware/drivers/audio/as3514.c | 4 +- firmware/export/config-e200.h | 6 +- firmware/export/config.h | 1 + firmware/export/tuner.h | 58 +- firmware/target/arm/pcm-pp.c | 4 +- .../target/arm/sandisk/sansa-e200/audio-e200.c | 9 - firmware/tuner_sanyo.c | 909 +++++++++++++++++++++ 12 files changed, 1039 insertions(+), 39 deletions(-) create mode 100644 firmware/tuner_sanyo.c diff --git a/apps/debug_menu.c b/apps/debug_menu.c index 2279a27c13..32ab46cbda 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -1990,16 +1990,48 @@ static bool dbg_fm_radio(void) lcd_setmargins(0, 0); + fm_detected = radio_hardware_present(); + while(1) { int row = 0; lcd_clear_display(); - fm_detected = radio_hardware_present(); snprintf(buf, sizeof buf, "HW detected: %s", fm_detected?"yes":"no"); lcd_puts(0, row++, buf); +#if (CONFIG_TUNER & LV24020LP) + if (fm_detected) + { + snprintf(buf, sizeof buf, "CTRL_STAT: %02X", + sanyo_get(RADIO_ALL) ); + lcd_puts(0, row++, buf); + + snprintf(buf, sizeof buf, "RADIO_STAT: %02X", + sanyo_get(RADIO_REG_STAT)); + lcd_puts(0, row++, buf); + + snprintf(buf, sizeof buf, "MSS_FM: %d kHz", + (sanyo_get(RADIO_MSS_FM) ) ); + lcd_puts(0, row++, buf); + + snprintf(buf, sizeof buf, "MSS_IF: %d Hz", + (sanyo_get(RADIO_MSS_IF) ) ); + lcd_puts(0, row++, buf); + + snprintf(buf, sizeof buf, "MSS_SD: %d Hz", + (sanyo_get(RADIO_MSS_SD) ) ); + lcd_puts(0, row++, buf); + + snprintf(buf, sizeof buf, "if_set: %d Hz", + (sanyo_get(RADIO_IF_SET) ) ); + lcd_puts(0, row++, buf); + snprintf(buf, sizeof buf, "sd_set: %d Hz", + (sanyo_get(RADIO_SD_SET) ) ); + lcd_puts(0, row++, buf); + } +#endif #if (CONFIG_TUNER & S1A0903X01) snprintf(buf, sizeof buf, "Samsung regs: %08X", samsung_get(RADIO_ALL)); diff --git a/apps/keymaps/keymap-e200.c b/apps/keymaps/keymap-e200.c index 55e30ca258..318f495abe 100644 --- a/apps/keymaps/keymap-e200.c +++ b/apps/keymaps/keymap-e200.c @@ -219,6 +219,17 @@ static const struct button_mapping button_context_recscreen[] = { LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) }; /* button_context_recscreen */ +/** FM Radio Screen **/ +static const struct button_mapping button_context_radio[] = { + { ACTION_FM_MENU, BUTTON_DOWN, BUTTON_NONE }, + { ACTION_FM_PRESET, BUTTON_SELECT, BUTTON_NONE }, + { ACTION_FM_STOP, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP }, + { ACTION_FM_MODE, BUTTON_REC, BUTTON_NONE }, + { ACTION_FM_EXIT, BUTTON_POWER, BUTTON_NONE }, + { ACTION_FM_PLAY, BUTTON_UP|BUTTON_REL, BUTTON_UP }, + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_SETTINGS) +}; /* button_context_radio */ + static const struct button_mapping button_context_keyboard[] = { { ACTION_KBD_LEFT, BUTTON_LEFT, BUTTON_NONE }, { ACTION_KBD_LEFT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, @@ -283,7 +294,9 @@ const struct button_mapping* get_context_mapping(int context) return button_context_settings_time; case CONTEXT_YESNOSCREEN: - return button_context_yesno; + return button_context_yesno; + case CONTEXT_FM: + return button_context_radio; case CONTEXT_BOOKMARKSCREEN: return button_context_bmark; case CONTEXT_QUICKSCREEN: diff --git a/apps/recorder/radio.c b/apps/recorder/radio.c index a0e6c81e3a..9f3228be1f 100644 --- a/apps/recorder/radio.c +++ b/apps/recorder/radio.c @@ -90,6 +90,13 @@ #elif CONFIG_KEYPAD == ONDIO_PAD #define FM_RECORD_DBLPRE #define FM_RECORD +#elif (CONFIG_KEYPAD == SANSA_E200_PAD) +#define FM_MENU +#define FM_PRESET +#define FM_STOP +#define FM_MODE +#define FM_EXIT +#define FM_PLAY #endif #define RADIO_SCAN_MODE 0 @@ -97,10 +104,14 @@ static const struct fm_region_setting fm_region[] = { /* Note: Desriptive strings are just for display atm and are not compiled. */ - FM_REGION_ENTRY("Europe", 87500000, 108000000, 50000, 0, 0), - FM_REGION_ENTRY("US/Canada", 87900000, 107900000, 200000, 1, 0), - FM_REGION_ENTRY("Japan", 76000000, 90000000, 100000, 0, 1), - FM_REGION_ENTRY("Korea", 87500000, 108000000, 100000, 0, 0), + [REGION_EUROPE] = + FM_REGION_ENTRY("Europe", 87500000, 108000000, 50000, 0, 0), + [REGION_US_CANADA] = + FM_REGION_ENTRY("US/Canada", 87900000, 107900000, 200000, 1, 0), + [REGION_JAPAN] = + FM_REGION_ENTRY("Japan", 76000000, 90000000, 100000, 0, 1), + [REGION_KOREA] = + FM_REGION_ENTRY("Korea", 87500000, 108000000, 100000, 0, 0), }; static int curr_preset = -1; @@ -158,13 +169,18 @@ bool in_radio_screen(void) return in_screen; } +/* TODO: Move some more of the control functionality to an HAL and clean up the + mess */ + /* secret flag for starting paused - prevents unmute */ #define FMRADIO_START_PAUSED 0x8000 void radio_start(void) { const struct fm_region_setting *fmr; bool start_paused; +#if CONFIG_TUNER != LV24020LP int mute_timeout; +#endif if(radio_status == FMRADIO_PLAYING) return; @@ -182,8 +198,14 @@ void radio_start(void) * fmr->freq_step + fmr->freq_min; radio_set(RADIO_SLEEP, 0); /* wake up the tuner */ +#if (CONFIG_TUNER & LV24020LP) + radio_set(RADIO_REGION, global_settings.fm_region); + radio_set(RADIO_FORCE_MONO, global_settings.fm_force_mono); +#endif radio_set(RADIO_FREQUENCY, curr_freq); +#if CONFIG_TUNER != LV24020LP + if(radio_status == FMRADIO_OFF) { #if (CONFIG_TUNER & S1A0903X01) @@ -209,6 +231,7 @@ void radio_start(void) break; yield(); } +#endif /* CONFIG_TUNER != LV24020LP */ /* keep radio from sounding initially */ if(!start_paused) @@ -1311,6 +1334,9 @@ void toggle_mono_mode(bool mono) void set_radio_region(int region) { +#if (CONFIG_TUNER & LV24020LP) + radio_set(RADIO_REGION, global_settings.fm_region); +#endif #if (CONFIG_TUNER & TEA5767) radio_set(RADIO_SET_DEEMPHASIS, fm_region[region].deemphasis); diff --git a/docs/CREDITS b/docs/CREDITS index d5ad63b3ef..5ac1fabcf9 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -297,6 +297,7 @@ Akio Idehara Dagni McPhee Alex Gerchanovsky Gerhard Dirschl +Ivan Zupan The libmad team The wavpack team The ffmpeg team diff --git a/firmware/SOURCES b/firmware/SOURCES index 70bd12e0d5..2b948e7df4 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -156,6 +156,9 @@ tuner_samsung.c drivers/fmradio_i2c.c tuner_philips.c #endif /* (CONFIG_TUNER & TEA5767) */ +#if (CONFIG_TUNER & LV24020LP) +tuner_sanyo.c +#endif /* (CONFIG_TUNER & LV24020LP) */ #endif /*SIMULATOR */ #endif /* CONFIG_TUNER */ diff --git a/firmware/drivers/audio/as3514.c b/firmware/drivers/audio/as3514.c index 982bbe16d3..12a72fe0dd 100644 --- a/firmware/drivers/audio/as3514.c +++ b/firmware/drivers/audio/as3514.c @@ -402,7 +402,6 @@ void audiohw_set_monitor(int enable) if (enable) { source = SOURCE_LINE_IN1_ANALOG; - audiohw_set_master_vol(as3514.vol_l, as3514.vol_r); /* LI1R_Mute_off */ line_in1_r |= (1 << 5); @@ -415,4 +414,7 @@ void audiohw_set_monitor(int enable) as3514_write(AUDIOSET1, audioset1); as3514_write(LINE_IN1_R, line_in1_r); as3514_write(LINE_IN1_L, line_in1_l); + + /* Sync mixer volume */ + audiohw_set_master_vol(as3514.vol_l, as3514.vol_r); } diff --git a/firmware/export/config-e200.h b/firmware/export/config-e200.h index 001c89b7d7..71b1270749 100644 --- a/firmware/export/config-e200.h +++ b/firmware/export/config-e200.h @@ -22,7 +22,7 @@ /* Define bitmask of input sources - recordable bitmask can be defined explicitly if different */ -#define INPUT_SRC_CAPS (SRC_CAP_MIC) +#define INPUT_SRC_CAPS (SRC_CAP_MIC | SRC_CAP_FMRADIO) /* define this if you have a bitmap LCD display */ #define HAVE_LCD_BITMAP @@ -88,8 +88,8 @@ #define AB_REPEAT_ENABLE 1 /* FM Tuner */ -/*#define CONFIG_TUNER TEA5767 -#define CONFIG_TUNER_XTAL 32768 *//* TODO: what is this? */ +#define CONFIG_TUNER LV24020LP +#define HAVE_TUNER_PWR_CTRL /* Define this for LCD backlight available */ #define HAVE_BACKLIGHT diff --git a/firmware/export/config.h b/firmware/export/config.h index dd4eaf4488..4652359e62 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -29,6 +29,7 @@ /* CONFIG_TUNER (note these are combineable bit-flags) */ #define S1A0903X01 0x01 /* Samsung */ #define TEA5767 0x02 /* Philips */ +#define LV24020LP 0x04 /* Sanyo */ /* CONFIG_CODEC */ #define MAS3587F 3587 diff --git a/firmware/export/tuner.h b/firmware/export/tuner.h index 2e286b4cbe..9f6d29f697 100644 --- a/firmware/export/tuner.h +++ b/firmware/export/tuner.h @@ -17,28 +17,42 @@ * KIND, either express or implied. * ****************************************************************************/ -#ifndef __TUNER_SAMSUNG_H__ -#define __TUNER_SAMSUNG_H__ +#ifndef __TUNER_H__ +#define __TUNER_H__ #include "hwcompat.h" /* settings to the tuner layer */ -#define RADIO_ALL -1 /* debug */ -#define RADIO_SLEEP 0 -#define RADIO_FREQUENCY 1 -#define RADIO_MUTE 2 -#define RADIO_IF_MEASUREMENT 3 -#define RADIO_SENSITIVITY 4 -#define RADIO_FORCE_MONO 5 -#define RADIO_SCAN_FREQUENCY 6 +#define RADIO_ALL -1 /* debug */ +#define RADIO_SLEEP 0 +#define RADIO_FREQUENCY 1 +#define RADIO_MUTE 2 +#define RADIO_IF_MEASUREMENT 3 +#define RADIO_SENSITIVITY 4 +#define RADIO_FORCE_MONO 5 +#define RADIO_SCAN_FREQUENCY 6 #if (CONFIG_TUNER & TEA5767) -#define RADIO_SET_DEEMPHASIS 7 -#define RADIO_SET_BAND 8 +#define RADIO_SET_DEEMPHASIS 7 +#define RADIO_SET_BAND 8 +#endif +#if (CONFIG_TUNER & LV24020LP) +#define RADIO_REGION 9 /* to be used for all tuners */ +#define RADIO_REG_STAT 100 +#define RADIO_MSS_FM 101 +#define RADIO_MSS_IF 102 +#define RADIO_MSS_SD 103 +#define RADIO_IF_SET 104 +#define RADIO_SD_SET 105 #endif /* readback from the tuner layer */ -#define RADIO_PRESENT 0 -#define RADIO_TUNED 1 -#define RADIO_STEREO 2 +#define RADIO_PRESENT 0 +#define RADIO_TUNED 1 +#define RADIO_STEREO 2 + +#define REGION_EUROPE 0 +#define REGION_US_CANADA 1 +#define REGION_JAPAN 2 +#define REGION_KOREA 3 #if CONFIG_TUNER @@ -49,6 +63,9 @@ int radio_get(int setting); #if CONFIG_TUNER == S1A0903X01 /* FM recorder */ #define radio_set samsung_set #define radio_get samsung_get +#elif CONFIG_TUNER == LV24020LP /* Sansa */ +#define radio_set sanyo_set +#define radio_get sanyo_get #elif CONFIG_TUNER == TEA5767 /* iRiver, iAudio */ #define radio_set philips_set #define radio_get philips_get @@ -57,14 +74,19 @@ int radio_get(int setting); #define radio_get _radio_get int (*_radio_set)(int setting, int value); int (*_radio_get)(int setting); -#endif -#endif +#endif /* CONFIG_TUNER == */ +#endif /* SIMULATOR */ #if (CONFIG_TUNER & S1A0903X01) int samsung_set(int setting, int value); int samsung_get(int setting); #endif /* CONFIG_TUNER & S1A0903X01 */ +#if (CONFIG_TUNER & LV24020LP) +int sanyo_set(int setting, int value); +int sanyo_get(int setting); +#endif /* CONFIG_TUNER & LV24020LP */ + #if (CONFIG_TUNER & TEA5767) struct philips_dbg_info { @@ -98,4 +120,4 @@ static inline void tuner_init(void) #endif /* #if CONFIG_TUNER */ -#endif +#endif /* __TUNER_H__ */ diff --git a/firmware/target/arm/pcm-pp.c b/firmware/target/arm/pcm-pp.c index 9027ff13b3..0608c208eb 100644 --- a/firmware/target/arm/pcm-pp.c +++ b/firmware/target/arm/pcm-pp.c @@ -377,7 +377,7 @@ void fiq_record(void) if (audio_channels == 2) { /* RX is stereo */ while (p_size > 0) { - if (FIFO_FREE_COUNT < 2) { + if (FIFO_FREE_COUNT < 8) { /* enable interrupt */ IISCONFIG |= (1 << 0); goto fiq_record_exit; @@ -401,7 +401,7 @@ void fiq_record(void) else { /* RX is left channel mono */ while (p_size > 0) { - if (FIFO_FREE_COUNT < 2) { + if (FIFO_FREE_COUNT < 8) { /* enable interrupt */ IISCONFIG |= (1 << 0); goto fiq_record_exit; diff --git a/firmware/target/arm/sandisk/sansa-e200/audio-e200.c b/firmware/target/arm/sandisk/sansa-e200/audio-e200.c index a3f3284b98..f046f0db99 100644 --- a/firmware/target/arm/sandisk/sansa-e200/audio-e200.c +++ b/firmware/target/arm/sandisk/sansa-e200/audio-e200.c @@ -42,11 +42,8 @@ void audio_set_output_source(int source) void audio_set_source(int source, unsigned flags) { static int last_source = AUDIO_SRC_PLAYBACK; -#if 0 static bool last_recording = false; bool recording = flags & SRCF_RECORDING; -#endif - (void)flags; switch (source) { @@ -70,13 +67,9 @@ void audio_set_source(int source, unsigned flags) } break; -#if 0 case AUDIO_SRC_FMRADIO: /* recording and playback */ audio_channels = 2; - if (!recording) - audiohw_set_recvol(23, 23, AUDIO_GAIN_LINEIN); - if (source == last_source && recording == last_recording) break; @@ -92,9 +85,7 @@ void audio_set_source(int source, unsigned flags) audiohw_disable_recording(); audiohw_set_monitor(true); /* line 1 analog audio path */ } - break; -#endif } /* end switch */ last_source = source; diff --git a/firmware/tuner_sanyo.c b/firmware/tuner_sanyo.c new file mode 100644 index 0000000000..1f4533b5c0 --- /dev/null +++ b/firmware/tuner_sanyo.c @@ -0,0 +1,909 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * Tuner driver for the Sanyo LV24020LP + * + * Copyright (C) 2007 Ivan Zupan + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include +#include +#include "config.h" +#include "thread.h" +#include "kernel.h" +#include "tuner.h" /* tuner abstraction interface */ +#include "fmradio.h" /* physical interface driver */ +#include "mpeg.h" +#include "sound.h" +#include "pp5024.h" +#include "system.h" +#include "as3514.h" + +#ifndef BOOTLOADER + +#if 0 +/* define to enable tuner logging */ +#define SANYO_TUNER_LOG +#endif + +#ifdef SANYO_TUNER_LOG +#include "sprintf.h" +#include "file.h" + +static int fd_log = -1; + +#define TUNER_LOG_OPEN() if (fd_log < 0) \ + fd_log = creat("/tuner_dump.txt") +/* syncing required because close() is never called */ +#define TUNER_LOG_SYNC() fsync(fd_log) +#define TUNER_LOG(s...) fdprintf(fd_log, s) +#else +#define TUNER_LOG_OPEN() +#define TUNER_LOG_SYNC() +#define TUNER_LOG(s...) +#endif /* SANYO_TUNER_LOG */ + +/** tuner register defines **/ + +/* pins on GPIOH port */ +#define FM_NRW_PIN 3 +#define FM_CLOCK_PIN 4 +#define FM_DATA_PIN 5 +#define FM_CLK_DELAY 1 + +/* block 1 registers */ + +/* R */ +#define CHIP_ID 0x00 + +/* W */ +#define BLK_SEL 0x01 + #define BLK1 0x01 + #define BLK2 0x02 + +/* W */ +#define MSRC_SEL 0x02 + #define MSR_O (1 << 7) + #define AFC_LVL (1 << 6) + #define AFC_SPD (1 << 5) + #define MSS_SD (1 << 2) + #define MSS_FM (1 << 1) + #define MSS_IF (1 << 0) + +/* W */ +#define FM_OSC 0x03 + +/* W */ +#define SD_OSC 0x04 + +/* W */ +#define IF_OSC 0x05 + +/* W */ +#define CNT_CTRL 0x06 + #define CNT1_CLR (1 << 7) + #define CTAB(x) ((x) & (0x7 << 4)) + #define CTAB_STOP_2 (0x0 << 4) + #define CTAB_STOP_8 (0x1 << 4) + #define CTAB_STOP_32 (0x2 << 4) + #define CTAB_STOP_128 (0x3 << 4) + #define CTAB_STOP_512 (0x4 << 4) + #define CTAB_STOP_2048 (0x5 << 4) + #define CTAB_STOP_8192 (0x6 << 4) + #define CTAB_STOP_32768 (0x7 << 4) + #define SWP_CNT_L (1 << 3) + #define CNT_EN (1 << 2) + #define CNT_SEL (1 << 1) + #define CNT_SET (1 << 0) + +/* W */ +#define IRQ_MSK 0x08 + #define IM_MS (1 << 6) + #define IRQ_LVL (1 << 3) + #define IM_AFC (1 << 2) + #define IM_FS (1 << 1) + #define IM_CNT2 (1 << 0) + +/* W */ +#define FM_CAP 0x09 + +/* R */ +#define CNT_L 0x0a /* Counter register low value */ + +/* R */ +#define CNT_H 0x0b /* Counter register high value */ + +/* R */ +#define CTRL_STAT 0x0c + #define AFC_FLG (1 << 0) + +/* R */ +#define RADIO_STAT 0x0d + #define RSS_MS (1 << 7) + #define RSS_FS(x) ((x) & 0x7f) + #define RSS_FS_GET(x) ((x) & 0x7f) + #define RSS_FS_SET(x) (x) +/* Note: Reading this register will clear field strength and mono/stereo interrupt. */ + +/* R */ +#define IRQ_ID 0x0e + #define II_CNT2 (1 << 5) + #define II_AFC (1 << 3) + #define II_FS_MS (1 << 0) + +/* W */ +#define IRQ_OUT 0x0f + +/* block 2 registers - offset added in order to id and avoid manual + switching */ +#define BLK2_START 0x10 + +/* W */ +#define RADIO_CTRL1 (0x02 + BLK2_START) + #define EN_MEAS (1 << 7) + #define EN_AFC (1 << 6) + #define DIR_AFC (1 << 3) + #define RST_AFC (1 << 2) + +/* W */ +#define IF_CENTER (0x03 + BLK2_START) + +/* W */ +#define IF_BW (0x05 + BLK2_START) + +/* W */ +#define RADIO_CTRL2 (0x06 + BLK2_START) + #define VREF2 (1 << 7) + #define VREF (1 << 6) + #define STABI_BP (1 << 5) + #define IF_PM_L (1 << 4) + #define AGCSP (1 << 1) + #define AM_ANT_BSW (1 << 0) /* ?? */ + +/* W */ +#define RADIO_CTRL3 (0x07 + BLK2_START) + #define AGC_SLVL (1 << 7) + #define VOLSH (1 << 6) + #define TB_ON (1 << 5) + #define AMUTE_L (1 << 4) + #define SE_FM (1 << 3) + #define SE_BE (1 << 1) + #define SE_EXT (1 << 0) /* For LV24000=0, LV24001/24002=Ext source enab. */ + +/* W */ +#define STEREO_CTRL (0x08 + BLK2_START) + #define FRCST (1 << 7) + #define FMCS(x) ((x) & (0x7 << 4)) + #define FMCS_GET(x) (((x) & (0x7 << 4)) >> 4) + #define FMCS_SET(x) ((x) << 4) + #define AUTOSSR (1 << 3) + #define PILTCA (1 << 2) + #define SD_PM (1 << 1) + #define ST_M (1 << 0) + +/* W */ +#define AUDIO_CTRL1 (0x09 + BLK2_START) + #define TONE_LVL(x) ((x) & (0xf << 4)) + #define TONE_LVL_GET(x) (((x) & (0xf << 4)) >> 4) + #define TONE_LVL_SET(x) ((x) << 4) + #define VOL_LVL(x) ((x) & 0xf) + #define VOL_LVL_GET(x) ((x) & 0xf) + #define VOL_LVL_SET(x) ((x) << 4) + +/* W */ +#define AUDIO_CTRL2 (0x0a + BLK2_START) + #define BASS_PP (1 << 0) + #define BASS_P (1 << 1) /* BASS_P, BASS_N are mutually-exclusive */ + #define BASS_N (1 << 2) + #define TREB_P (1 << 3) /* TREB_P, TREB_N are mutually-exclusive */ + #define TREB_N (1 << 4) + #define DEEMP (1 << 5) + #define BPFREQ(x) ((x) & (0x3 << 6)) + #define BPFREQ_2_0K (0x0 << 6) + #define BPFREQ_1_0K (0x1 << 6) + #define BPFREQ_0_5K (0x2 << 6) + #define BPFREQ_HIGH (0x3 << 6) + +/* W */ +#define PW_SCTRL (0x0b + BLK2_START) + #define SS_CTRL(x) ((x) & (0x7 << 5)) + #define SS_CTRL_GET(x) (((x) & (0x7 << 5)) >> 5) + #define SS_CTRL_SET(x) ((x) << 5) + #define SM_CTRL(x) ((x) & (0x7 << 2)) + #define SM_CTRL_GET(x) (((x) & (0x7 << 2)) >> 2) + #define SM_CTRL_SET(x) ((x) << 2) + #define PW_HPA (1 << 1) /* LV24002 only */ + #define PW_RAD (1 << 0) + +/* shadow for writeable registers */ +#define TUNER_POWERED (1 << 0) +#define TUNER_PRESENT (1 << 1) +#define TUNER_AWAKE (1 << 2) +#define TUNER_PRESENCE_CHECKED (1 << 3) +static unsigned tuner_status = 0; + +static unsigned char sanyo_regs[0x1c]; + +static const int sw_osc_low = 10; /* 30; */ +static const int sw_osc_high = 240; /* 200; */ +static const int sw_cap_low = 0; +static const int sw_cap_high = 191; + +/* linear coefficients used for tuning */ +static int coef_00, coef_01, coef_10, coef_11; + +/* DAC control register set values */ +int if_set, sd_set; + +static inline bool tuner_awake(void) +{ + return (tuner_status & TUNER_AWAKE) != 0; +} + +/* send a byte to the tuner - expects write mode to be current */ +static void tuner_sanyo_send_byte(unsigned int byte) +{ + int i; + + byte <<= FM_DATA_PIN; + + for (i = 0; i < 8; i++) + { + GPIOH_OUTPUT_VAL &= ~(1 << FM_CLOCK_PIN); + + GPIOH_OUTPUT_VAL = (GPIOH_OUTPUT_VAL & ~(1 << FM_DATA_PIN)) | + (byte & (1 << FM_DATA_PIN)); + + GPIOH_OUTPUT_VAL |= (1 << FM_CLOCK_PIN); + udelay(FM_CLK_DELAY); + + byte >>= 1; + } +} + +/* end a write cycle on the tuner */ +static void tuner_sanyo_end_write(void) +{ + /* switch back to read mode */ + GPIOH_OUTPUT_EN &= ~(1 << FM_DATA_PIN); + GPIOH_OUTPUT_VAL &= ~(1 << FM_NRW_PIN); +} + +/* prepare a write cycle on the tuner */ +static unsigned int tuner_sanyo_begin_write(unsigned int address) +{ + /* Get register's block, translate address */ + unsigned int blk = (address >= BLK2_START) ? + (address -= BLK2_START, BLK2) : BLK1; + + for (;;) + { + /* Prepare 3-wire bus pins for write cycle */ + GPIOH_OUTPUT_VAL |= (1 << FM_NRW_PIN); + GPIOH_OUTPUT_EN |= (1 << FM_DATA_PIN); + + udelay(FM_CLK_DELAY); + + /* current block == register block? */ + if (blk == sanyo_regs[BLK_SEL]) + return address; + + /* switch block */ + sanyo_regs[BLK_SEL] = blk; + + /* data first */ + tuner_sanyo_send_byte(blk); + /* then address */ + tuner_sanyo_send_byte(BLK_SEL); + + tuner_sanyo_end_write(); + + udelay(FM_CLK_DELAY); + } +} + +/* write a byte to a tuner register */ +static void tuner_sanyo_write(unsigned int address, unsigned int data) +{ + /* shadow logical values but do logical=>physical remappings on some + registers' data. */ + sanyo_regs[address] = data; + + switch (address) + { + case FM_OSC: + /* L: 000..255 + * P: 255..000 */ + data = 255 - data; + break; + case FM_CAP: + /* L: 000..063, 064..191 + * P: 255..192, 127..000 */ + data = ((data < 64) ? 255 : (255 - 64)) - data; + break; + case RADIO_CTRL1: + /* L: data + * P: data | always "1" bits */ + data |= (1 << 4) | (1 << 1) | (1 << 0); + break; + } + + address = tuner_sanyo_begin_write(address); + + /* data first */ + tuner_sanyo_send_byte(data); + /* then address */ + tuner_sanyo_send_byte(address); + + tuner_sanyo_end_write(); +} + +/* helpers to set/clear register bits */ +static void tuner_sanyo_write_or(unsigned int address, unsigned int bits) +{ + tuner_sanyo_write(address, sanyo_regs[address] | bits); +} + +static void tuner_sanyo_write_and(unsigned int address, unsigned int bits) +{ + tuner_sanyo_write(address, sanyo_regs[address] & bits); +} + +/* read a byte from a tuner register */ +static unsigned int tuner_sanyo_read(unsigned int address) +{ + int i; + unsigned int toread; + + address = tuner_sanyo_begin_write(address); + + /* address */ + tuner_sanyo_send_byte(address); + + tuner_sanyo_end_write(); + + /* data */ + toread = 0; + for (i = 0; i < 8; i++) + { + GPIOH_OUTPUT_VAL &= ~(1 << FM_CLOCK_PIN); + udelay(FM_CLK_DELAY); + + toread |= (GPIOH_INPUT_VAL & (1 << FM_DATA_PIN)) << i; + + GPIOH_OUTPUT_VAL |= (1 << FM_CLOCK_PIN); + } + + return toread >> FM_DATA_PIN; +} + +/* enables auto frequency centering */ +static void enable_afc(bool enabled) +{ + unsigned int radio_ctrl1 = sanyo_regs[RADIO_CTRL1]; + + if (enabled) + { + radio_ctrl1 &= ~RST_AFC; + radio_ctrl1 |= EN_AFC; + } + else + { + radio_ctrl1 |= RST_AFC; + radio_ctrl1 &= ~EN_AFC; + } + + tuner_sanyo_write(RADIO_CTRL1, radio_ctrl1); +} + +static int calculate_coef(unsigned fkhz) +{ + /* Overflow below 66000kHz -- + My tuner tunes down to a min of ~72600kHz but datasheet mentions + 66000kHz as the minimum. ?? Perhaps 76000kHz was intended? */ + return fkhz < 66000 ? + 0x7fffffff : 0x81d1a47efc5cb700ull / ((uint64_t)fkhz*fkhz); +} + +static int interpolate_x(int expected_y, int x1, int x2, int y1, int y2) +{ + return y1 == y2 ? + 0 : (int64_t)(expected_y - y1)*(x2 - x1) / (y2 - y1) + x1; +} + +static int interpolate_y(int expected_x, int x1, int x2, int y1, int y2) +{ + return x1 == x2 ? + 0 : (int64_t)(expected_x - x1)*(y2 - y1) / (x2 - x1) + y1; +} + +/* this performs measurements of IF, FM and Stereo frequencies + * Input can be: MSS_FM, MSS_IF, MSS_SD */ +static int tuner_measure(unsigned char type, int scale, int duration) +{ + int64_t finval; + + if (!tuner_awake()) + return 0; + + /* enable measuring */ + tuner_sanyo_write_or(MSRC_SEL, type); + tuner_sanyo_write_and(CNT_CTRL, ~CNT_SEL); + tuner_sanyo_write_or(RADIO_CTRL1, EN_MEAS); + + /* reset counter */ + tuner_sanyo_write_or(CNT_CTRL, CNT1_CLR); + tuner_sanyo_write_and(CNT_CTRL, ~CNT1_CLR); + + /* start counter, delay for specified time and stop it */ + tuner_sanyo_write_or(CNT_CTRL, CNT_EN); + udelay(duration*1000 - 16); + tuner_sanyo_write_and(CNT_CTRL, ~CNT_EN); + + /* read tick count */ + finval = (tuner_sanyo_read(CNT_H) << 8) | tuner_sanyo_read(CNT_L); + + /* restore measure mode */ + tuner_sanyo_write_and(RADIO_CTRL1, ~EN_MEAS); + tuner_sanyo_write_and(MSRC_SEL, ~type); + + /* convert value */ + if (type == MSS_FM) + finval = scale*finval*256 / duration; + else + finval = scale*finval / duration; + + return (int)finval; +} + +/* set the FM oscillator frequency */ +static void sanyo_set_frequency(int freq) +{ + int coef, cap_value, osc_value; + int f1, f2, x1, x2; + int count; + + if (!tuner_awake()) + return; + + TUNER_LOG_OPEN(); + + TUNER_LOG("set_frequency(%d)\n", freq); + + enable_afc(false); + + /* MHz -> kHz */ + freq /= 1000; + + TUNER_LOG("Select cap:\n"); + + coef = calculate_coef(freq); + cap_value = interpolate_x(coef, sw_cap_low, sw_cap_high, + coef_00, coef_01); + + osc_value = sw_osc_low; + tuner_sanyo_write(FM_OSC, osc_value); + + /* Just in case - don't go into infinite loop */ + for (count = 0; count < 30; count++) + { + int y0 = interpolate_y(cap_value, sw_cap_low, sw_cap_high, + coef_00, coef_01); + int y1 = interpolate_y(cap_value, sw_cap_low, sw_cap_high, + coef_10, coef_11); + int coef_fcur, cap_new, coef_cor, range; + + tuner_sanyo_write(FM_CAP, cap_value); + + range = y1 - y0; + f1 = tuner_measure(MSS_FM, 1, 16); + coef_fcur = calculate_coef(f1); + coef_cor = calculate_coef((f1*1000 + 32*256) / 1000); + y0 = coef_cor; + y1 = y0 + range; + + TUNER_LOG("%d %d %d %d %d %d %d %d\n", + f1, cap_value, coef, coef_fcur, coef_cor, y0, y1, range); + + if (coef >= y0 && coef <= y1) + { + osc_value = interpolate_x(coef, sw_osc_low, sw_osc_high, + y0, y1); + + if (osc_value >= sw_osc_low && osc_value <= sw_osc_high) + break; + } + + cap_new = interpolate_x(coef, cap_value, sw_cap_high, + coef_fcur, coef_01); + + if (cap_new == cap_value) + { + if (coef < coef_fcur) + cap_value++; + else + cap_value--; + } + else + { + cap_value = cap_new; + } + } + + TUNER_LOG("osc_value: %d\n", osc_value); + + TUNER_LOG("Tune:\n"); + + x1 = sw_osc_low, x2 = sw_osc_high; + /* FM_OSC already at SW_OSC low and f1 is already the measured + frequency */ + + do + { + int x2_new; + + tuner_sanyo_write(FM_OSC, x2); + f2 = tuner_measure(MSS_FM, 1, 16); + + if (abs(f2 - freq) <= 16) + { + TUNER_LOG("%d %d %d %d\n", f1, f2, x1, x2); + break; + } + + x2_new = interpolate_x(freq, x1, x2, f1, f2); + + x1 = x2, f1 = f2, x2 = x2_new; + TUNER_LOG("%d %d %d %d\n", f1, f2, x1, x2); + } + while (x2 != 0); + + if (x2 == 0) + { + /* May still be close enough */ + TUNER_LOG("tuning failed - diff: %d\n", f2 - freq); + } + + enable_afc(true); + + TUNER_LOG("\n"); + + TUNER_LOG_SYNC(); +} + +static void fine_step_tune(int (*setcmp)(int regval), int regval, int step) +{ + /* Registers are not always stable, timeout if best fit not found soon + enough */ + unsigned long abort = current_tick + HZ*2; + int flags = 0; + + while (TIME_BEFORE(current_tick, abort)) + { + int cmp; + + regval = regval + step; + + cmp = setcmp(regval); + + if (cmp == 0) + break; + + step = abs(step); + + if (cmp < 0) + { + flags |= 1; + if (step == 1) + flags |= 4; + } + else + { + step = -step; + flags |= 2; + if (step == -1) + step |= 8; + } + + if ((flags & 0xc) == 0xc) + break; + + if ((flags & 0x3) == 0x3) + { + step /= 2; + if (step == 0) + step = 1; + flags &= ~3; + } + } +} + +static int if_setcmp(int regval) +{ + tuner_sanyo_write(IF_OSC, regval); + tuner_sanyo_write(IF_CENTER, regval); + tuner_sanyo_write(IF_BW, 65*regval/100); + + if_set = tuner_measure(MSS_IF, 1000, 32); + + /* This register is bounces around by a few hundred Hz and doesn't seem + to be precisely tuneable. Just do 110000 +/- 500 since it's not very + critical it seems. */ + if (abs(if_set - 109500) <= 500) + return 0; + + return if_set < 109500 ? -1 : 1; +} + +static int sd_setcmp(int regval) +{ + tuner_sanyo_write(SD_OSC, regval); + + sd_set = tuner_measure(MSS_SD, 1000, 32); + + if (abs(sd_set - 38300) <= 31) + return 0; + + return sd_set < 38300 ? -1 : 1; +} + +static void sanyo_sleep(bool sleep) +{ + if (sleep || tuner_awake()) + return; + + if ((tuner_status & (TUNER_PRESENT | TUNER_POWERED)) != + (TUNER_PRESENT | TUNER_POWERED)) + return; + + tuner_status |= TUNER_AWAKE; + + enable_afc(false); + + /* 2. Calibrate the IF frequency at 110 kHz: */ + tuner_sanyo_write_and(RADIO_CTRL2, ~IF_PM_L); + fine_step_tune(if_setcmp, 0x80, 8); + tuner_sanyo_write_or(RADIO_CTRL2, IF_PM_L); + + /* 3. Calibrate the stereo decoder clock at 38.3 kHz: */ + tuner_sanyo_write_or(STEREO_CTRL, SD_PM); + fine_step_tune(sd_setcmp, 0x80, 8); + tuner_sanyo_write_and(STEREO_CTRL, ~SD_PM); + + /* calculate FM tuning coefficients */ + tuner_sanyo_write(FM_CAP, sw_cap_low); + tuner_sanyo_write(FM_OSC, sw_osc_low); + coef_00 = calculate_coef(tuner_measure(MSS_FM, 1, 64)); + + tuner_sanyo_write(FM_CAP, sw_cap_high); + coef_01 = calculate_coef(tuner_measure(MSS_FM, 1, 64)); + + tuner_sanyo_write(FM_CAP, sw_cap_low); + tuner_sanyo_write(FM_OSC, sw_osc_high); + coef_10 = calculate_coef(tuner_measure(MSS_FM, 1, 64)); + + tuner_sanyo_write(FM_CAP, sw_cap_high); + coef_11 = calculate_coef(tuner_measure(MSS_FM, 1, 64)); + + /* set various audio level settings */ + tuner_sanyo_write(AUDIO_CTRL1, TONE_LVL_SET(0) | VOL_LVL_SET(0)); + tuner_sanyo_write_or(RADIO_CTRL2, AGCSP); + tuner_sanyo_write_or(RADIO_CTRL3, VOLSH); + tuner_sanyo_write(STEREO_CTRL, FMCS_SET(7) | AUTOSSR); + tuner_sanyo_write(PW_SCTRL, SS_CTRL_SET(3) | SM_CTRL_SET(1) | + PW_RAD); +} + +/** Public interfaces **/ +bool radio_power(bool status) +{ + static const unsigned char tuner_defaults[][2] = + { + /* Block 1 writeable registers */ + { MSRC_SEL, AFC_LVL }, + { FM_OSC, 0x80 }, + { SD_OSC, 0x80 }, + { IF_OSC, 0x80 }, + { CNT_CTRL, CNT1_CLR | SWP_CNT_L }, + { IRQ_MSK, 0x00 }, /* IRQ_LVL -> Low to High */ + { FM_CAP, 0x80 }, + /* { IRQ_OUT, 0x00 }, No action on this register (skip) */ + /* Block 2 writeable registers */ + { RADIO_CTRL1, EN_AFC }, + { IF_CENTER, 0x80 }, + { IF_BW, 65*0x80 / 100 }, /* 65% of IF_OSC */ + { RADIO_CTRL2, IF_PM_L }, + { RADIO_CTRL3, AGC_SLVL | SE_FM }, + { STEREO_CTRL, FMCS_SET(4) | AUTOSSR }, + { AUDIO_CTRL1, TONE_LVL_SET(7) | VOL_LVL_SET(7) }, + { AUDIO_CTRL2, BPFREQ_HIGH }, /* deemphasis 50us */ + { PW_SCTRL, SS_CTRL_SET(3) | SM_CTRL_SET(3) | PW_RAD }, + }; + + unsigned i; + bool powered = tuner_status & TUNER_POWERED; + + if (status == powered) + return powered; + + if (status) + { + /* init mystery amplification device */ + outl(inl(0x70000084) | 0x1, 0x70000084); + outl(inl(0x70000080) | 0x4, 0x70000080); + udelay(5); + + /* When power up, host should initialize the 3-wire bus in host read + mode: */ + + /* 1. Set direction of the DATA-line to input-mode. */ + GPIOH_OUTPUT_EN &= ~(1 << FM_DATA_PIN); + GPIOH_ENABLE |= (1 << FM_DATA_PIN); + + /* 2. Drive NR_W low */ + GPIOH_OUTPUT_VAL &= ~(1 << FM_NRW_PIN); + GPIOH_OUTPUT_EN |= (1 << FM_NRW_PIN); + GPIOH_ENABLE |= (1 << FM_NRW_PIN); + + /* 3. Drive CLOCK high */ + GPIOH_OUTPUT_VAL |= (1 << FM_CLOCK_PIN); + GPIOH_OUTPUT_EN |= (1 << FM_CLOCK_PIN); + GPIOH_ENABLE |= (1 << FM_CLOCK_PIN); + + tuner_status |= TUNER_POWERED; + + /* if tuner is present, CHIP ID is 0x09 */ + if (tuner_sanyo_read(CHIP_ID) == 0x09) + { + tuner_status |= TUNER_PRESENT; + + /* After power-up, the LV2400x needs to be initialized as + follows: */ + + /* 1. Write default values to the registers: */ + sanyo_regs[BLK_SEL] = 0; /* Force a switch on the first */ + for (i = 0; i < ARRAYLEN(tuner_defaults); i++) + tuner_sanyo_write(tuner_defaults[i][0], tuner_defaults[i][1]); + + /* Complete the startup calibration if the tuner is woken */ + udelay(100000); + } + } + else + { + /* Power off and set all as inputs */ + if (tuner_status & TUNER_PRESENT) + tuner_sanyo_write_and(PW_SCTRL, ~PW_RAD); + + GPIOH_OUTPUT_EN &= ~((1 << FM_DATA_PIN) | (1 << FM_NRW_PIN) | + (1 << FM_CLOCK_PIN)); + GPIOH_ENABLE &= ~((1 << FM_DATA_PIN) | (1 << FM_NRW_PIN) | + (1 << FM_CLOCK_PIN)); + + outl(inl(0x70000084) & ~0x1, 0x70000084); + + tuner_status &= ~(TUNER_POWERED | TUNER_AWAKE); + } + + return powered; +} + +bool radio_powered(void) +{ + return (tuner_status & TUNER_POWERED) != 0; +} + +int sanyo_set(int setting, int value) +{ + int val = 1; + + switch(setting) + { + case RADIO_SLEEP: + sanyo_sleep(value); + break; + + case RADIO_FREQUENCY: + sanyo_set_frequency(value); + break; + + case RADIO_SCAN_FREQUENCY: + /* TODO: really implement this */ + sanyo_set_frequency(value); + val = sanyo_get(RADIO_TUNED); + break; + + case RADIO_MUTE: + if (value) + tuner_sanyo_write_and(RADIO_CTRL3, ~AMUTE_L); + else + tuner_sanyo_write_or(RADIO_CTRL3, AMUTE_L); + break; + + case RADIO_REGION: + switch (value) + { + case REGION_EUROPE: + case REGION_JAPAN: + case REGION_KOREA: + tuner_sanyo_write_and(AUDIO_CTRL2, ~DEEMP); + break; + case REGION_US_CANADA: + tuner_sanyo_write_or(AUDIO_CTRL2, DEEMP); + break; + default: + val = -1; + } + break; + + case RADIO_FORCE_MONO: + if (value) + tuner_sanyo_write_or(STEREO_CTRL, ST_M); + else + tuner_sanyo_write_and(STEREO_CTRL, ~ST_M); + break; + + default: + val = -1; + } + + return val; +} + +int sanyo_get(int setting) +{ + int val = -1; + + switch(setting) + { + case RADIO_ALL: + return tuner_sanyo_read(CTRL_STAT); + + case RADIO_TUNED: + /* TODO: really implement this */ + val = RSS_FS(tuner_sanyo_read(RADIO_STAT)) < 0x1f; + break; + + case RADIO_STEREO: + val = (tuner_sanyo_read(RADIO_STAT) & RSS_MS) != 0; + break; + + case RADIO_PRESENT: + val = (tuner_status & TUNER_PRESENT) != 0; + break; + + /* tuner-specific debug info */ + case RADIO_REG_STAT: + return tuner_sanyo_read(RADIO_STAT); + + case RADIO_MSS_FM: + return tuner_measure(MSS_FM, 1, 16); + + case RADIO_MSS_IF: + return tuner_measure(MSS_IF, 1000, 16); + + case RADIO_MSS_SD: + return tuner_measure(MSS_SD, 1000, 16); + + case RADIO_IF_SET: + return if_set; + + case RADIO_SD_SET: + return sd_set; + } + + return val; +} +#endif /* BOOTLOADER */ -- cgit v1.2.3