From 65f61d6cce4a5d3b52860d38a922b01fcddc70cd Mon Sep 17 00:00:00 2001 From: Nils Wallménius Date: Tue, 9 Dec 2008 20:48:04 +0000 Subject: 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. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: svn://svn.rockbox.org/rockbox/trunk@19372 a1c6a512-1295-4272-9138-f99709370657 --- apps/keymaps/keymap-gigabeat-s.c | 18 +++ apps/recorder/radio.c | 6 +- firmware/SOURCES | 2 + firmware/drivers/audio/wm8978.c | 12 +- firmware/drivers/tuner/si4700.c | 151 ++++++++++++++++++++- firmware/export/audio.h | 1 + firmware/export/config-gigabeat-s.h | 7 +- firmware/export/si4700.h | 12 ++ firmware/export/wm8978.h | 3 + .../target/arm/imx31/gigabeat-s/audio-gigabeat-s.c | 56 ++++++++ .../arm/imx31/gigabeat-s/fmradio-i2c-gigabeat-s.c | 49 +++++++ firmware/target/arm/imx31/gigabeat-s/power-imx31.c | 30 ++++ firmware/tuner.c | 16 +++ 13 files changed, 354 insertions(+), 9 deletions(-) create mode 100644 firmware/target/arm/imx31/gigabeat-s/audio-gigabeat-s.c create mode 100644 firmware/target/arm/imx31/gigabeat-s/fmradio-i2c-gigabeat-s.c diff --git a/apps/keymaps/keymap-gigabeat-s.c b/apps/keymaps/keymap-gigabeat-s.c index a60fd2d8a1..b0cc87011f 100644 --- a/apps/keymaps/keymap-gigabeat-s.c +++ b/apps/keymaps/keymap-gigabeat-s.c @@ -276,6 +276,22 @@ static const struct button_mapping button_context_keyboard[] = { LAST_ITEM_IN_LIST }; /* 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) { switch (context) @@ -319,6 +335,8 @@ const struct button_mapping* get_context_mapping(int context) return button_context_pitchscreen; case CONTEXT_KEYBOARD: return button_context_keyboard; + case CONTEXT_FM: + return button_context_radio; } return button_context_standard; } diff --git a/apps/recorder/radio.c b/apps/recorder/radio.c index 5f9fff8d64..dee38b5475 100644 --- a/apps/recorder/radio.c +++ b/apps/recorder/radio.c @@ -104,13 +104,17 @@ #define FM_MODE #define FM_EXIT #define FM_PLAY + +#elif (CONFIG_KEYPAD == GIGABEAT_S_PAD) +#define FM_PRESET +#define FM_MODE #endif #define RADIO_SCAN_MODE 0 #define RADIO_PRESET_MODE 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 search_dir = 0; diff --git a/firmware/SOURCES b/firmware/SOURCES index 68d10876c8..1fce6cfdb5 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -764,6 +764,8 @@ target/arm/imx31/gigabeat-s/usb-imx31.c target/arm/imx31/gigabeat-s/wmcodec-imx31.c #ifndef BOOTLOADER 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 /* SIMULATOR */ #endif /* GIGABEAT_S */ diff --git a/firmware/drivers/audio/wm8978.c b/firmware/drivers/audio/wm8978.c index c2c19ca7be..6a7c974f43 100644 --- a/firmware/drivers/audio/wm8978.c +++ b/firmware/drivers/audio/wm8978.c @@ -144,12 +144,12 @@ static void wmc_write(unsigned int reg, unsigned int 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); } -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); } @@ -226,6 +226,14 @@ void audiohw_postinit(void) wmc_write(WMC_AUDIO_INTERFACE, WMC_WL_16 | WMC_FMT_I2S); 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 */ wmc_write_masked(WMC_CLOCK_GEN_CTRL, WMC_BCLKDIV_4 | WMC_MS, WMC_BCLKDIV | WMC_MS | WMC_CLKSEL); diff --git a/firmware/drivers/tuner/si4700.c b/firmware/drivers/tuner/si4700.c index 9233afae24..a55a8cfcc4 100644 --- a/firmware/drivers/tuner/si4700.c +++ b/firmware/drivers/tuner/si4700.c @@ -9,7 +9,7 @@ * * 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 * modify it under the terms of the GNU General Public License @@ -29,19 +29,160 @@ #include "fmradio.h" #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 */ int si4700_set(int setting, int value) { - (void)setting; - (void)value; + switch(setting) + { + 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; } /* tuner abstraction layer: read something from the tuner */ 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 */ + + switch(setting) + { + case RADIO_PRESENT: + val = tuner_present ? 1 : 0; + break; - return -1; + 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; } + diff --git a/firmware/export/audio.h b/firmware/export/audio.h index 661247df2f..4f9ef1a4e1 100644 --- a/firmware/export/audio.h +++ b/firmware/export/audio.h @@ -23,6 +23,7 @@ #include #include +#include "config.h" /* 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 many files. */ diff --git a/firmware/export/config-gigabeat-s.h b/firmware/export/config-gigabeat-s.h index 72b1cb76a8..c893061d0d 100644 --- a/firmware/export/config-gigabeat-s.h +++ b/firmware/export/config-gigabeat-s.h @@ -69,9 +69,14 @@ /* The number of bytes reserved for loadable plugins */ #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 HAVE_WM8978 +#define INPUT_SRC_CAPS SRC_CAP_FMRADIO + #define HW_SAMPR_CAPS (SAMPR_CAP_48 | SAMPR_CAP_44 | SAMPR_CAP_32 | \ SAMPR_CAP_24 | SAMPR_CAP_22 | SAMPR_CAP_16 | \ SAMPR_CAP_12 | SAMPR_CAP_11 | SAMPR_CAP_8) @@ -114,7 +119,7 @@ /* Define the bitmask of modules used */ #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 this if target has an additional number of threads specific to it */ diff --git a/firmware/export/si4700.h b/firmware/export/si4700.h index a740ae03ab..5b8001f753 100644 --- a/firmware/export/si4700.h +++ b/firmware/export/si4700.h @@ -25,6 +25,18 @@ #ifndef _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_get(int setting); diff --git a/firmware/export/wm8978.h b/firmware/export/wm8978.h index f4ed46a6e2..9c54ae354d 100644 --- a/firmware/export/wm8978.h +++ b/firmware/export/wm8978.h @@ -30,6 +30,9 @@ int tenthdb2master(int db); void audiohw_set_headphone_vol(int vol_l, int vol_r); 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 /* Registers */ diff --git a/firmware/target/arm/imx31/gigabeat-s/audio-gigabeat-s.c b/firmware/target/arm/imx31/gigabeat-s/audio-gigabeat-s.c new file mode 100644 index 0000000000..6dd90bfdb7 --- /dev/null +++ b/firmware/target/arm/imx31/gigabeat-s/audio-gigabeat-s.c @@ -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; + } +} + diff --git a/firmware/target/arm/imx31/gigabeat-s/fmradio-i2c-gigabeat-s.c b/firmware/target/arm/imx31/gigabeat-s/fmradio-i2c-gigabeat-s.c new file mode 100644 index 0000000000..5e5c4853dd --- /dev/null +++ b/firmware/target/arm/imx31/gigabeat-s/fmradio-i2c-gigabeat-s.c @@ -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; +} + diff --git a/firmware/target/arm/imx31/gigabeat-s/power-imx31.c b/firmware/target/arm/imx31/gigabeat-s/power-imx31.c index de7f5800e6..07e6462bfb 100644 --- a/firmware/target/arm/imx31/gigabeat-s/power-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/power-imx31.c @@ -26,6 +26,9 @@ #include "backlight-target.h" #include "avic-imx31.h" #include "mc13783.h" +#include "i2c-imx31.h" + +extern struct i2c_node si4700_i2c_node; static bool charger_detect = false; @@ -79,6 +82,33 @@ bool ide_powered(void) 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) { /* Cut backlight */ diff --git a/firmware/tuner.c b/firmware/tuner.c index 6a7c1b4237..22cb981038 100644 --- a/firmware/tuner.c +++ b/firmware/tuner.c @@ -59,6 +59,16 @@ const struct tea5767_region_data tea5767_region_data[TUNER_NUM_REGIONS] = }; #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 int (*tuner_set)(int setting, int value); int (*tuner_get)(int setting); @@ -95,6 +105,12 @@ void tuner_init(void) s1a0903x01_set, s1a0903x01_get) #endif + #if (CONFIG_TUNER & SI4700) + TUNER_TYPE_CASE(SI4700, + si4700_set, + si4700_get, + si4700_init()) + #endif } } -- cgit v1.2.3