1
0
Fork 0
forked from len0rd/rockbox

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
This commit is contained in:
Michael Sevakis 2007-06-06 19:23:48 +00:00
parent af4cd0a84c
commit 21a4a87ca2
12 changed files with 1039 additions and 39 deletions

View file

@ -1990,16 +1990,48 @@ static bool dbg_fm_radio(void)
lcd_setmargins(0, 0); lcd_setmargins(0, 0);
fm_detected = radio_hardware_present();
while(1) while(1)
{ {
int row = 0; int row = 0;
lcd_clear_display(); lcd_clear_display();
fm_detected = radio_hardware_present();
snprintf(buf, sizeof buf, "HW detected: %s", fm_detected?"yes":"no"); snprintf(buf, sizeof buf, "HW detected: %s", fm_detected?"yes":"no");
lcd_puts(0, row++, buf); 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) #if (CONFIG_TUNER & S1A0903X01)
snprintf(buf, sizeof buf, "Samsung regs: %08X", snprintf(buf, sizeof buf, "Samsung regs: %08X",
samsung_get(RADIO_ALL)); samsung_get(RADIO_ALL));

View file

@ -219,6 +219,17 @@ static const struct button_mapping button_context_recscreen[] = {
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_recscreen */ }; /* 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[] = { static const struct button_mapping button_context_keyboard[] = {
{ ACTION_KBD_LEFT, BUTTON_LEFT, BUTTON_NONE }, { ACTION_KBD_LEFT, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_KBD_LEFT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, { ACTION_KBD_LEFT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
@ -284,6 +295,8 @@ const struct button_mapping* get_context_mapping(int context)
case CONTEXT_YESNOSCREEN: case CONTEXT_YESNOSCREEN:
return button_context_yesno; return button_context_yesno;
case CONTEXT_FM:
return button_context_radio;
case CONTEXT_BOOKMARKSCREEN: case CONTEXT_BOOKMARKSCREEN:
return button_context_bmark; return button_context_bmark;
case CONTEXT_QUICKSCREEN: case CONTEXT_QUICKSCREEN:

View file

@ -90,6 +90,13 @@
#elif CONFIG_KEYPAD == ONDIO_PAD #elif CONFIG_KEYPAD == ONDIO_PAD
#define FM_RECORD_DBLPRE #define FM_RECORD_DBLPRE
#define FM_RECORD #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 #endif
#define RADIO_SCAN_MODE 0 #define RADIO_SCAN_MODE 0
@ -97,10 +104,14 @@
static const struct fm_region_setting fm_region[] = { static const struct fm_region_setting fm_region[] = {
/* Note: Desriptive strings are just for display atm and are not compiled. */ /* Note: Desriptive strings are just for display atm and are not compiled. */
FM_REGION_ENTRY("Europe", 87500000, 108000000, 50000, 0, 0), [REGION_EUROPE] =
FM_REGION_ENTRY("US/Canada", 87900000, 107900000, 200000, 1, 0), FM_REGION_ENTRY("Europe", 87500000, 108000000, 50000, 0, 0),
FM_REGION_ENTRY("Japan", 76000000, 90000000, 100000, 0, 1), [REGION_US_CANADA] =
FM_REGION_ENTRY("Korea", 87500000, 108000000, 100000, 0, 0), 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; static int curr_preset = -1;
@ -158,13 +169,18 @@ bool in_radio_screen(void)
return in_screen; 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 */ /* secret flag for starting paused - prevents unmute */
#define FMRADIO_START_PAUSED 0x8000 #define FMRADIO_START_PAUSED 0x8000
void radio_start(void) void radio_start(void)
{ {
const struct fm_region_setting *fmr; const struct fm_region_setting *fmr;
bool start_paused; bool start_paused;
#if CONFIG_TUNER != LV24020LP
int mute_timeout; int mute_timeout;
#endif
if(radio_status == FMRADIO_PLAYING) if(radio_status == FMRADIO_PLAYING)
return; return;
@ -182,8 +198,14 @@ void radio_start(void)
* fmr->freq_step + fmr->freq_min; * fmr->freq_step + fmr->freq_min;
radio_set(RADIO_SLEEP, 0); /* wake up the tuner */ 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); radio_set(RADIO_FREQUENCY, curr_freq);
#if CONFIG_TUNER != LV24020LP
if(radio_status == FMRADIO_OFF) if(radio_status == FMRADIO_OFF)
{ {
#if (CONFIG_TUNER & S1A0903X01) #if (CONFIG_TUNER & S1A0903X01)
@ -209,6 +231,7 @@ void radio_start(void)
break; break;
yield(); yield();
} }
#endif /* CONFIG_TUNER != LV24020LP */
/* keep radio from sounding initially */ /* keep radio from sounding initially */
if(!start_paused) if(!start_paused)
@ -1311,6 +1334,9 @@ void toggle_mono_mode(bool mono)
void set_radio_region(int region) void set_radio_region(int region)
{ {
#if (CONFIG_TUNER & LV24020LP)
radio_set(RADIO_REGION, global_settings.fm_region);
#endif
#if (CONFIG_TUNER & TEA5767) #if (CONFIG_TUNER & TEA5767)
radio_set(RADIO_SET_DEEMPHASIS, radio_set(RADIO_SET_DEEMPHASIS,
fm_region[region].deemphasis); fm_region[region].deemphasis);

View file

@ -297,6 +297,7 @@ Akio Idehara
Dagni McPhee Dagni McPhee
Alex Gerchanovsky Alex Gerchanovsky
Gerhard Dirschl Gerhard Dirschl
Ivan Zupan
The libmad team The libmad team
The wavpack team The wavpack team
The ffmpeg team The ffmpeg team

View file

@ -156,6 +156,9 @@ tuner_samsung.c
drivers/fmradio_i2c.c drivers/fmradio_i2c.c
tuner_philips.c tuner_philips.c
#endif /* (CONFIG_TUNER & TEA5767) */ #endif /* (CONFIG_TUNER & TEA5767) */
#if (CONFIG_TUNER & LV24020LP)
tuner_sanyo.c
#endif /* (CONFIG_TUNER & LV24020LP) */
#endif /*SIMULATOR */ #endif /*SIMULATOR */
#endif /* CONFIG_TUNER */ #endif /* CONFIG_TUNER */

View file

@ -402,7 +402,6 @@ void audiohw_set_monitor(int enable)
if (enable) { if (enable) {
source = SOURCE_LINE_IN1_ANALOG; source = SOURCE_LINE_IN1_ANALOG;
audiohw_set_master_vol(as3514.vol_l, as3514.vol_r);
/* LI1R_Mute_off */ /* LI1R_Mute_off */
line_in1_r |= (1 << 5); line_in1_r |= (1 << 5);
@ -415,4 +414,7 @@ void audiohw_set_monitor(int enable)
as3514_write(AUDIOSET1, audioset1); as3514_write(AUDIOSET1, audioset1);
as3514_write(LINE_IN1_R, line_in1_r); as3514_write(LINE_IN1_R, line_in1_r);
as3514_write(LINE_IN1_L, line_in1_l); as3514_write(LINE_IN1_L, line_in1_l);
/* Sync mixer volume */
audiohw_set_master_vol(as3514.vol_l, as3514.vol_r);
} }

View file

@ -22,7 +22,7 @@
/* Define bitmask of input sources - recordable bitmask can be defined /* Define bitmask of input sources - recordable bitmask can be defined
explicitly if different */ 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 this if you have a bitmap LCD display */
#define HAVE_LCD_BITMAP #define HAVE_LCD_BITMAP
@ -88,8 +88,8 @@
#define AB_REPEAT_ENABLE 1 #define AB_REPEAT_ENABLE 1
/* FM Tuner */ /* FM Tuner */
/*#define CONFIG_TUNER TEA5767 #define CONFIG_TUNER LV24020LP
#define CONFIG_TUNER_XTAL 32768 *//* TODO: what is this? */ #define HAVE_TUNER_PWR_CTRL
/* Define this for LCD backlight available */ /* Define this for LCD backlight available */
#define HAVE_BACKLIGHT #define HAVE_BACKLIGHT

View file

@ -29,6 +29,7 @@
/* CONFIG_TUNER (note these are combineable bit-flags) */ /* CONFIG_TUNER (note these are combineable bit-flags) */
#define S1A0903X01 0x01 /* Samsung */ #define S1A0903X01 0x01 /* Samsung */
#define TEA5767 0x02 /* Philips */ #define TEA5767 0x02 /* Philips */
#define LV24020LP 0x04 /* Sanyo */
/* CONFIG_CODEC */ /* CONFIG_CODEC */
#define MAS3587F 3587 #define MAS3587F 3587

View file

@ -17,28 +17,42 @@
* KIND, either express or implied. * KIND, either express or implied.
* *
****************************************************************************/ ****************************************************************************/
#ifndef __TUNER_SAMSUNG_H__ #ifndef __TUNER_H__
#define __TUNER_SAMSUNG_H__ #define __TUNER_H__
#include "hwcompat.h" #include "hwcompat.h"
/* settings to the tuner layer */ /* settings to the tuner layer */
#define RADIO_ALL -1 /* debug */ #define RADIO_ALL -1 /* debug */
#define RADIO_SLEEP 0 #define RADIO_SLEEP 0
#define RADIO_FREQUENCY 1 #define RADIO_FREQUENCY 1
#define RADIO_MUTE 2 #define RADIO_MUTE 2
#define RADIO_IF_MEASUREMENT 3 #define RADIO_IF_MEASUREMENT 3
#define RADIO_SENSITIVITY 4 #define RADIO_SENSITIVITY 4
#define RADIO_FORCE_MONO 5 #define RADIO_FORCE_MONO 5
#define RADIO_SCAN_FREQUENCY 6 #define RADIO_SCAN_FREQUENCY 6
#if (CONFIG_TUNER & TEA5767) #if (CONFIG_TUNER & TEA5767)
#define RADIO_SET_DEEMPHASIS 7 #define RADIO_SET_DEEMPHASIS 7
#define RADIO_SET_BAND 8 #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 #endif
/* readback from the tuner layer */ /* readback from the tuner layer */
#define RADIO_PRESENT 0 #define RADIO_PRESENT 0
#define RADIO_TUNED 1 #define RADIO_TUNED 1
#define RADIO_STEREO 2 #define RADIO_STEREO 2
#define REGION_EUROPE 0
#define REGION_US_CANADA 1
#define REGION_JAPAN 2
#define REGION_KOREA 3
#if CONFIG_TUNER #if CONFIG_TUNER
@ -49,6 +63,9 @@ int radio_get(int setting);
#if CONFIG_TUNER == S1A0903X01 /* FM recorder */ #if CONFIG_TUNER == S1A0903X01 /* FM recorder */
#define radio_set samsung_set #define radio_set samsung_set
#define radio_get samsung_get #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 */ #elif CONFIG_TUNER == TEA5767 /* iRiver, iAudio */
#define radio_set philips_set #define radio_set philips_set
#define radio_get philips_get #define radio_get philips_get
@ -57,14 +74,19 @@ int radio_get(int setting);
#define radio_get _radio_get #define radio_get _radio_get
int (*_radio_set)(int setting, int value); int (*_radio_set)(int setting, int value);
int (*_radio_get)(int setting); int (*_radio_get)(int setting);
#endif #endif /* CONFIG_TUNER == */
#endif #endif /* SIMULATOR */
#if (CONFIG_TUNER & S1A0903X01) #if (CONFIG_TUNER & S1A0903X01)
int samsung_set(int setting, int value); int samsung_set(int setting, int value);
int samsung_get(int setting); int samsung_get(int setting);
#endif /* CONFIG_TUNER & S1A0903X01 */ #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) #if (CONFIG_TUNER & TEA5767)
struct philips_dbg_info struct philips_dbg_info
{ {
@ -98,4 +120,4 @@ static inline void tuner_init(void)
#endif /* #if CONFIG_TUNER */ #endif /* #if CONFIG_TUNER */
#endif #endif /* __TUNER_H__ */

View file

@ -377,7 +377,7 @@ void fiq_record(void)
if (audio_channels == 2) { if (audio_channels == 2) {
/* RX is stereo */ /* RX is stereo */
while (p_size > 0) { while (p_size > 0) {
if (FIFO_FREE_COUNT < 2) { if (FIFO_FREE_COUNT < 8) {
/* enable interrupt */ /* enable interrupt */
IISCONFIG |= (1 << 0); IISCONFIG |= (1 << 0);
goto fiq_record_exit; goto fiq_record_exit;
@ -401,7 +401,7 @@ void fiq_record(void)
else { else {
/* RX is left channel mono */ /* RX is left channel mono */
while (p_size > 0) { while (p_size > 0) {
if (FIFO_FREE_COUNT < 2) { if (FIFO_FREE_COUNT < 8) {
/* enable interrupt */ /* enable interrupt */
IISCONFIG |= (1 << 0); IISCONFIG |= (1 << 0);
goto fiq_record_exit; goto fiq_record_exit;

View file

@ -42,11 +42,8 @@ void audio_set_output_source(int source)
void audio_set_source(int source, unsigned flags) void audio_set_source(int source, unsigned flags)
{ {
static int last_source = AUDIO_SRC_PLAYBACK; static int last_source = AUDIO_SRC_PLAYBACK;
#if 0
static bool last_recording = false; static bool last_recording = false;
bool recording = flags & SRCF_RECORDING; bool recording = flags & SRCF_RECORDING;
#endif
(void)flags;
switch (source) switch (source)
{ {
@ -70,13 +67,9 @@ void audio_set_source(int source, unsigned flags)
} }
break; break;
#if 0
case AUDIO_SRC_FMRADIO: /* recording and playback */ case AUDIO_SRC_FMRADIO: /* recording and playback */
audio_channels = 2; audio_channels = 2;
if (!recording)
audiohw_set_recvol(23, 23, AUDIO_GAIN_LINEIN);
if (source == last_source && recording == last_recording) if (source == last_source && recording == last_recording)
break; break;
@ -92,9 +85,7 @@ void audio_set_source(int source, unsigned flags)
audiohw_disable_recording(); audiohw_disable_recording();
audiohw_set_monitor(true); /* line 1 analog audio path */ audiohw_set_monitor(true); /* line 1 analog audio path */
} }
break; break;
#endif
} /* end switch */ } /* end switch */
last_source = source; last_source = source;

909
firmware/tuner_sanyo.c Normal file
View file

@ -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 <stdbool.h>
#include <stdlib.h>
#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 */