1
0
Fork 0
forked from len0rd/rockbox

FS#9609 FM radio support for the Gigabeat S, seeking/scanning is not yet

implemented but manual tuning works nicely. Thanks to Rafaël Carré,
Bertrik Sikken and Robert Menes for suggestions and debugging help.



git-svn-id: svn://svn.rockbox.org/rockbox/trunk@19372 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Nils Wallménius 2008-12-09 20:48:04 +00:00
parent a13c162719
commit 65f61d6cce
13 changed files with 354 additions and 9 deletions

View file

@ -276,6 +276,22 @@ static const struct button_mapping button_context_keyboard[] = {
LAST_ITEM_IN_LIST LAST_ITEM_IN_LIST
}; /* button_context_keyboard */ }; /* button_context_keyboard */
static const struct button_mapping button_context_radio[] = {
{ ACTION_FM_MENU, BUTTON_SELECT | BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_FM_PRESET, BUTTON_SELECT | BUTTON_REL, BUTTON_SELECT },
{ ACTION_FM_STOP, BUTTON_POWER, BUTTON_NONE },
{ ACTION_FM_MODE, BUTTON_MENU, BUTTON_NONE },
{ ACTION_FM_EXIT, BUTTON_BACK, BUTTON_NONE },
{ ACTION_FM_PLAY, BUTTON_PLAY, BUTTON_NONE },
{ ACTION_SETTINGS_INC, BUTTON_VOL_UP, BUTTON_NONE },
{ ACTION_SETTINGS_INCREPEAT, BUTTON_VOL_UP|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_SETTINGS_DEC, BUTTON_VOL_DOWN, BUTTON_NONE },
{ ACTION_SETTINGS_DECREPEAT, BUTTON_VOL_DOWN|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_SETTINGS)
};
const struct button_mapping* get_context_mapping(int context) const struct button_mapping* get_context_mapping(int context)
{ {
switch (context) switch (context)
@ -319,6 +335,8 @@ const struct button_mapping* get_context_mapping(int context)
return button_context_pitchscreen; return button_context_pitchscreen;
case CONTEXT_KEYBOARD: case CONTEXT_KEYBOARD:
return button_context_keyboard; return button_context_keyboard;
case CONTEXT_FM:
return button_context_radio;
} }
return button_context_standard; return button_context_standard;
} }

View file

@ -104,13 +104,17 @@
#define FM_MODE #define FM_MODE
#define FM_EXIT #define FM_EXIT
#define FM_PLAY #define FM_PLAY
#elif (CONFIG_KEYPAD == GIGABEAT_S_PAD)
#define FM_PRESET
#define FM_MODE
#endif #endif
#define RADIO_SCAN_MODE 0 #define RADIO_SCAN_MODE 0
#define RADIO_PRESET_MODE 1 #define RADIO_PRESET_MODE 1
static int curr_preset = -1; static int curr_preset = -1;
static int curr_freq; static int curr_freq; /* current frequency in Hz */
static int radio_mode = RADIO_SCAN_MODE; static int radio_mode = RADIO_SCAN_MODE;
static int search_dir = 0; static int search_dir = 0;

View file

@ -764,6 +764,8 @@ target/arm/imx31/gigabeat-s/usb-imx31.c
target/arm/imx31/gigabeat-s/wmcodec-imx31.c target/arm/imx31/gigabeat-s/wmcodec-imx31.c
#ifndef BOOTLOADER #ifndef BOOTLOADER
target/arm/imx31/gigabeat-s/pcm-imx31.c target/arm/imx31/gigabeat-s/pcm-imx31.c
target/arm/imx31/gigabeat-s/audio-gigabeat-s.c
target/arm/imx31/gigabeat-s/fmradio-i2c-gigabeat-s.c
#endif #endif
#endif /* SIMULATOR */ #endif /* SIMULATOR */
#endif /* GIGABEAT_S */ #endif /* GIGABEAT_S */

View file

@ -144,12 +144,12 @@ static void wmc_write(unsigned int reg, unsigned int val)
wmcodec_write(reg, val); wmcodec_write(reg, val);
} }
static void wmc_set(unsigned int reg, unsigned int bits) void wmc_set(unsigned int reg, unsigned int bits)
{ {
wmc_write(reg, wmc_regs[reg] | bits); wmc_write(reg, wmc_regs[reg] | bits);
} }
static void wmc_clear(unsigned int reg, unsigned int bits) void wmc_clear(unsigned int reg, unsigned int bits)
{ {
wmc_write(reg, wmc_regs[reg] & ~bits); wmc_write(reg, wmc_regs[reg] & ~bits);
} }
@ -226,6 +226,14 @@ void audiohw_postinit(void)
wmc_write(WMC_AUDIO_INTERFACE, WMC_WL_16 | WMC_FMT_I2S); wmc_write(WMC_AUDIO_INTERFACE, WMC_WL_16 | WMC_FMT_I2S);
wmc_write(WMC_DAC_CONTROL, WMC_DACOSR_128 | WMC_AMUTE); wmc_write(WMC_DAC_CONTROL, WMC_DACOSR_128 | WMC_AMUTE);
wmc_set(WMC_INPUT_CTRL, WMC_R2_2INPPGA | WMC_L2_2INPPGA);
wmc_set(WMC_LEFT_INP_PGA_GAIN_CTRL, 0x3f);
wmc_set(WMC_RIGHT_INP_PGA_GAIN_CTRL, 0x3f);
wmc_set(WMC_LEFT_INP_PGA_GAIN_CTRL, 1<<8);
wmc_set(WMC_RIGHT_INP_PGA_GAIN_CTRL, 1<<8);
wmc_set(WMC_LEFT_ADC_BOOST_CTRL, (7<<3));
wmc_set(WMC_RIGHT_ADC_BOOST_CTRL, (7<<3));
/* Specific to HW clocking */ /* Specific to HW clocking */
wmc_write_masked(WMC_CLOCK_GEN_CTRL, WMC_BCLKDIV_4 | WMC_MS, wmc_write_masked(WMC_CLOCK_GEN_CTRL, WMC_BCLKDIV_4 | WMC_MS,
WMC_BCLKDIV | WMC_MS | WMC_CLKSEL); WMC_BCLKDIV | WMC_MS | WMC_CLKSEL);

View file

@ -9,7 +9,7 @@
* *
* Tuner "middleware" for Silicon Labs SI4700 chip * Tuner "middleware" for Silicon Labs SI4700 chip
* *
* Copyright (C) 2008 ??? * Copyright (C) 2008 Nils Wallménius
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -29,19 +29,160 @@
#include "fmradio.h" #include "fmradio.h"
#include "fmradio_i2c.h" /* physical interface driver */ #include "fmradio_i2c.h" /* physical interface driver */
#define I2C_ADR 0x20
/* I2C writes start at register 02h so the first two bytes are
02h, next two 03h, etc. */
static unsigned char write_bytes[8]; /* registers 02 - 05 */
static bool tuner_present = false;
void si4700_init(void)
{
unsigned char read_bytes[32];
tuner_power(true);
fmradio_i2c_read(I2C_ADR, read_bytes, sizeof(read_bytes));
if ((read_bytes[12] << 8 | read_bytes[13]) == 0x1242)
{
tuner_present = true;
/* fill in the initial values in write_bytes */
memcpy(&write_bytes[0], &read_bytes[16], sizeof(write_bytes));
/* -6dB volume, keep everything else as default */
write_bytes[7] = (write_bytes[7] & ~0xf) | 0xc;
}
tuner_power(false);
}
static void si4700_tune(void)
{
unsigned char read_bytes[1];
write_bytes[2] |= (1 << 7); /* Set TUNE high to start tuning */
fmradio_i2c_write(I2C_ADR, write_bytes, sizeof(write_bytes));
do
{
sleep(HZ/50);
fmradio_i2c_read(I2C_ADR, read_bytes, 1);
}
while (!(read_bytes[0] & (1 << 6))); /* STC high == Seek/Tune complete */
write_bytes[2] &= ~(1 << 7); /* Set TUNE low */
fmradio_i2c_write(I2C_ADR, write_bytes, sizeof(write_bytes));
}
/* tuner abstraction layer: set something to the tuner */ /* tuner abstraction layer: set something to the tuner */
int si4700_set(int setting, int value) int si4700_set(int setting, int value)
{ {
(void)setting; switch(setting)
(void)value; {
case RADIO_SLEEP:
if (value)
{
write_bytes[1] = (1 | (1 << 6)); /* ENABLE high, DISABLE high */
}
else
{
write_bytes[1] = 1; /* ENABLE high, DISABLE low */
}
break;
case RADIO_FREQUENCY:
{
static const unsigned int spacings[3] =
{
200000, 100000, 50000
};
unsigned int chan;
unsigned int spacing = spacings[(write_bytes[7] >> 4) & 3] ;
if (write_bytes[7] & (3 << 6)) /* check BAND */
{
chan = (value - 76000000) / spacing;
}
else
{
chan = (value - 87500000) / spacing;
}
write_bytes[2] = (write_bytes[2] & ~3) | ((chan & (3 << 8)) >> 8);
write_bytes[3] = (chan & 0xff);
fmradio_i2c_write(I2C_ADR, write_bytes, sizeof(write_bytes));
si4700_tune();
return 1;
}
case RADIO_SCAN_FREQUENCY:
si4700_set(RADIO_FREQUENCY, value);
return 1;
case RADIO_MUTE:
if (value)
{
/* mute */
write_bytes[0] &= ~(1 << 6);
}
else
{
/* unmute */
write_bytes[0] |= (1 << 6);
}
break;
case RADIO_REGION:
{
const struct si4700_region_data *rd =
&si4700_region_data[value];
write_bytes[4] = ((write_bytes[4] & ~(1 << 3)) | (rd->deemphasis << 3));
write_bytes[7] = ((write_bytes[7] & ~(3 << 6)) | (rd->band << 6));
write_bytes[7] = ((write_bytes[7] & ~(3 << 4)) | (rd->spacing << 4));
break;
}
case RADIO_FORCE_MONO:
if (value)
{
write_bytes[0] |= (1 << 5);
}
else
{
write_bytes[0] &= ~(1 << 5);
}
break;
default:
return -1;
}
fmradio_i2c_write(I2C_ADR, write_bytes, sizeof(write_bytes));
return 1; return 1;
} }
/* tuner abstraction layer: read something from the tuner */ /* tuner abstraction layer: read something from the tuner */
int si4700_get(int setting) int si4700_get(int setting)
{ {
(void)setting; /* I2C reads start with register 0xA */
unsigned char read_bytes[1];
int val = -1; /* default for unsupported query */
return -1; switch(setting)
{
case RADIO_PRESENT:
val = tuner_present ? 1 : 0;
break;
case RADIO_TUNED:
val = 1;
break;
case RADIO_STEREO:
fmradio_i2c_read(I2C_ADR, read_bytes, sizeof(read_bytes));
val = (read_bytes[0] & 1); /* ST high == Stereo */
break;
}
return val;
} }

View file

@ -23,6 +23,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <sys/types.h> #include <sys/types.h>
#include "config.h"
/* These must always be included with audio.h for this to compile under /* These must always be included with audio.h for this to compile under
cetain conditions. Do it here or else spread the complication around to cetain conditions. Do it here or else spread the complication around to
many files. */ many files. */

View file

@ -69,9 +69,14 @@
/* The number of bytes reserved for loadable plugins */ /* The number of bytes reserved for loadable plugins */
#define PLUGIN_BUFFER_SIZE 0x80000 #define PLUGIN_BUFFER_SIZE 0x80000
/* Define this if you have a SI4700 fm radio tuner */
#define CONFIG_TUNER SI4700
/* Define this if you have the WM8978 audio codec */ /* Define this if you have the WM8978 audio codec */
#define HAVE_WM8978 #define HAVE_WM8978
#define INPUT_SRC_CAPS SRC_CAP_FMRADIO
#define HW_SAMPR_CAPS (SAMPR_CAP_48 | SAMPR_CAP_44 | SAMPR_CAP_32 | \ #define HW_SAMPR_CAPS (SAMPR_CAP_48 | SAMPR_CAP_44 | SAMPR_CAP_32 | \
SAMPR_CAP_24 | SAMPR_CAP_22 | SAMPR_CAP_16 | \ SAMPR_CAP_24 | SAMPR_CAP_22 | SAMPR_CAP_16 | \
SAMPR_CAP_12 | SAMPR_CAP_11 | SAMPR_CAP_8) SAMPR_CAP_12 | SAMPR_CAP_11 | SAMPR_CAP_8)
@ -114,7 +119,7 @@
/* Define the bitmask of modules used */ /* Define the bitmask of modules used */
#define SPI_MODULE_MASK (USE_CSPI2_MODULE) #define SPI_MODULE_MASK (USE_CSPI2_MODULE)
#define I2C_MODULE_MASK (USE_I2C1_MODULE) #define I2C_MODULE_MASK (USE_I2C1_MODULE | USE_I2C2_MODULE)
#define GPIO_EVENT_MASK (USE_GPIO1_EVENTS) #define GPIO_EVENT_MASK (USE_GPIO1_EVENTS)
/* Define this if target has an additional number of threads specific to it */ /* Define this if target has an additional number of threads specific to it */

View file

@ -25,6 +25,18 @@
#ifndef _SI4700_H_ #ifndef _SI4700_H_
#define _SI4700_H_ #define _SI4700_H_
#define HAVE_RADIO_REGION
struct si4700_region_data
{
unsigned char deemphasis; /* 0: 50us, 1: 75us */
unsigned char band; /* 0: us/europe, 1: japan */
unsigned char spacing; /* 0: us/australia (200kHz), 1: europe/japan (100kHz), 2: (50kHz) */
} __attribute__((packed));
extern const struct si4700_region_data si4700_region_data[TUNER_NUM_REGIONS];
void si4700_init(void);
int si4700_set(int setting, int value); int si4700_set(int setting, int value);
int si4700_get(int setting); int si4700_get(int setting);

View file

@ -30,6 +30,9 @@ int tenthdb2master(int db);
void audiohw_set_headphone_vol(int vol_l, int vol_r); void audiohw_set_headphone_vol(int vol_l, int vol_r);
void audiohw_set_frequency(int sampling_control); void audiohw_set_frequency(int sampling_control);
void wmc_set(unsigned int reg, unsigned int bits);
void wmc_clear(unsigned int reg, unsigned int bits);
#define WMC_I2C_ADDR 0x34 #define WMC_I2C_ADDR 0x34
/* Registers */ /* Registers */

View file

@ -0,0 +1,56 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2008 by Nils Wallménius
*
* 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 "wm8978.h"
#include "audio.h"
void audio_set_output_source(int source)
{
(void)source; /* TODO */
}
void audio_input_mux(int source, unsigned int flags)
{
(void)flags;
switch (source)
{
case AUDIO_SRC_PLAYBACK:
/* deselect bypass patths and set volume to -15dB */
wmc_clear(WMC_LEFT_MIXER_CTRL, (WMC_BYPL2LMIX) | (7<<2));
wmc_clear(WMC_RIGHT_MIXER_CTRL, (WMC_BYPR2RMIX) | (7<<2));
/* disable L2/R2 inputs and boost stage */
wmc_clear(WMC_POWER_MANAGEMENT2,
WMC_INPPGAENR | WMC_INPPGAENL | WMC_BOOSTENL | WMC_BOOSTENR);
break;
case AUDIO_SRC_FMRADIO:
/* enable L2/R2 inputs and boost stage */
wmc_set(WMC_POWER_MANAGEMENT2,
WMC_INPPGAENR | WMC_INPPGAENL | WMC_BOOSTENL | WMC_BOOSTENR);
/* select bypass patths and set volume to 0dB */
wmc_set(WMC_LEFT_MIXER_CTRL, (WMC_BYPL2LMIX) | (5<<2));
wmc_set(WMC_RIGHT_MIXER_CTRL, (WMC_BYPR2RMIX) | (5<<2));
break;
default:
source = AUDIO_SRC_PLAYBACK;
}
}

View file

@ -0,0 +1,49 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
* Physical interface of the SI4700 in the Gigabeat S
*
* Copyright (C) 2008 by Nils Wallménius
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "config.h"
#include "system.h"
#include "i2c-imx31.h"
#include "fmradio_i2c.h"
struct i2c_node si4700_i2c_node =
{
.num = I2C2_NUM,
.ifdr = I2C_IFDR_DIV192, /* 66MHz/.4MHz = 165, closest = 192 = 343750Hz */
/* Just hard-code for now - scaling may require
* updating */
.addr = (0x20),
};
int fmradio_i2c_write(unsigned char address, const unsigned char* buf, int count)
{
(void)address;
i2c_write(&si4700_i2c_node, buf, count);
return 0;
}
int fmradio_i2c_read(unsigned char address, unsigned char* buf, int count)
{
(void)address;
i2c_read(&si4700_i2c_node, -1, buf, count);
return 0;
}

View file

@ -26,6 +26,9 @@
#include "backlight-target.h" #include "backlight-target.h"
#include "avic-imx31.h" #include "avic-imx31.h"
#include "mc13783.h" #include "mc13783.h"
#include "i2c-imx31.h"
extern struct i2c_node si4700_i2c_node;
static bool charger_detect = false; static bool charger_detect = false;
@ -79,6 +82,33 @@ bool ide_powered(void)
return (GPIO3_DR & (1 << 5)) != 0; return (GPIO3_DR & (1 << 5)) != 0;
} }
#if CONFIG_TUNER
bool tuner_power(bool status)
{
if (status)
{
/* the si4700 is the only thing connected to i2c2 so
we can diable the i2c module when not in use */
i2c_enable_node(&si4700_i2c_node, true);
/* enable the fm chip */
imx31_regmod32(&GPIO1_DR, (1 << 26), (1 << 26));
/* enable CLK32KMCU clock */
mc13783_set(MC13783_POWER_CONTROL0, MC13783_CLK32KMCUEN);
}
else
{
/* the si4700 is the only thing connected to i2c2 so
we can diable the i2c module when not in use */
i2c_enable_node(&si4700_i2c_node, false);
/* disable the fm chip */
imx31_regmod32(&GPIO1_DR, 0, (1 << 26));
/* disable CLK32KMCU clock */
mc13783_clear(MC13783_POWER_CONTROL0, MC13783_CLK32KMCUEN);
}
return true;
}
#endif /* #if CONFIG_TUNER */
void power_off(void) void power_off(void)
{ {
/* Cut backlight */ /* Cut backlight */

View file

@ -59,6 +59,16 @@ const struct tea5767_region_data tea5767_region_data[TUNER_NUM_REGIONS] =
}; };
#endif /* (CONFIG_TUNER & TEA5767) */ #endif /* (CONFIG_TUNER & TEA5767) */
#if (CONFIG_TUNER & SI4700)
const struct si4700_region_data si4700_region_data[TUNER_NUM_REGIONS] =
{
[REGION_EUROPE] = { 0, 0, 2 }, /* 50uS, US/Europe band, 50kHz spacing */
[REGION_US_CANADA] = { 1, 0, 0 }, /* 75uS, US/Europe band, 200kHz spacing */
[REGION_JAPAN] = { 0, 1, 1 }, /* 50uS, Japanese band, 100kHz spacing */
[REGION_KOREA] = { 0, 0, 1 }, /* 50uS, US/Europe band, 100kHz spacing */
};
#endif /* (CONFIG_TUNER & SI4700) */
#ifdef CONFIG_TUNER_MULTI #ifdef CONFIG_TUNER_MULTI
int (*tuner_set)(int setting, int value); int (*tuner_set)(int setting, int value);
int (*tuner_get)(int setting); int (*tuner_get)(int setting);
@ -95,6 +105,12 @@ void tuner_init(void)
s1a0903x01_set, s1a0903x01_set,
s1a0903x01_get) s1a0903x01_get)
#endif #endif
#if (CONFIG_TUNER & SI4700)
TUNER_TYPE_CASE(SI4700,
si4700_set,
si4700_get,
si4700_init())
#endif
} }
} }