2010-06-15 20:57:48 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* __________ __ ___.
|
|
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
|
|
* \/ \/ \/ \/ \/
|
|
|
|
* $Id$
|
|
|
|
*
|
2010-06-16 20:29:08 +00:00
|
|
|
* Tuner "middleware" for RDA5802 chip present in some Sansa Clip+ players
|
2010-06-15 20:57:48 +00:00
|
|
|
*
|
|
|
|
* Copyright (C) 2010 Bertrik Sikken
|
|
|
|
* Copyright (C) 2008 Nils Wallménius (si4700 code that this was based on)
|
|
|
|
*
|
|
|
|
* 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 <stdbool.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "kernel.h"
|
|
|
|
#include "tuner.h" /* tuner abstraction interface */
|
|
|
|
#include "fmradio.h"
|
|
|
|
#include "fmradio_i2c.h" /* physical interface driver */
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
#define SEEK_THRESHOLD 0x16
|
2010-06-15 20:57:48 +00:00
|
|
|
|
|
|
|
#define I2C_ADR 0x20
|
|
|
|
|
|
|
|
/** Registers and bits **/
|
|
|
|
#define POWERCFG 0x2
|
|
|
|
#define CHANNEL 0x3
|
|
|
|
#define SYSCONFIG1 0x4
|
|
|
|
#define SYSCONFIG2 0x5
|
|
|
|
#define SYSCONFIG3 0x6
|
|
|
|
|
|
|
|
#define READCHAN 0xA
|
|
|
|
#define STATUSRSSI 0xB
|
|
|
|
#define IDENT 0xC
|
|
|
|
|
|
|
|
|
|
|
|
/* POWERCFG (0x2) */
|
|
|
|
#define POWERCFG_DMUTE (0x1 << 14)
|
|
|
|
#define POWERCFG_MONO (0x1 << 13)
|
2010-06-16 20:29:08 +00:00
|
|
|
#define POWERCFG_SOFT_RESET (0x1 << 1)
|
2010-06-15 20:57:48 +00:00
|
|
|
#define POWERCFG_ENABLE (0x1 << 0)
|
|
|
|
|
|
|
|
/* CHANNEL (0x3) */
|
|
|
|
#define CHANNEL_CHAN (0x3ff << 6)
|
|
|
|
#define CHANNEL_CHANw(x) (((x) << 6) & CHANNEL_CHAN)
|
|
|
|
#define CHANNEL_TUNE (0x1 << 4)
|
|
|
|
#define CHANNEL_BAND (0x3 << 2)
|
|
|
|
#define CHANNEL_BANDw(x) (((x) << 2) & CHANNEL_BAND)
|
|
|
|
#define CHANNEL_BANDr(x) (((x) & CHANNEL_BAND) >> 2)
|
2010-07-26 20:15:16 +00:00
|
|
|
#define CHANNEL_BAND_870_1080 (0x0) /* tenth-megahertz */
|
|
|
|
#define CHANNEL_BAND_760_1080 (0x1)
|
|
|
|
#define CHANNEL_BAND_760_900 (0x2)
|
|
|
|
#define CHANNEL_BAND_650_760 (0x3)
|
2010-06-15 20:57:48 +00:00
|
|
|
#define CHANNEL_SPACE (0x3 << 0)
|
|
|
|
#define CHANNEL_SPACEw(x) (((x) << 0) & CHANNEL_SPACE)
|
|
|
|
#define CHANNEL_SPACEr(x) (((x) & CHANNEL_SPACE) >> 0)
|
2010-07-26 20:15:16 +00:00
|
|
|
#define CHANNEL_SPACE_100KHZ (0x0)
|
|
|
|
#define CHANNEL_SPACE_200KHZ (0x1)
|
|
|
|
#define CHANNEL_SPACE_50KHZ (0x2)
|
2010-06-15 20:57:48 +00:00
|
|
|
|
|
|
|
/* SYSCONFIG1 (0x4) */
|
|
|
|
#define SYSCONFIG1_DE (0x1 << 11)
|
2010-06-16 20:29:08 +00:00
|
|
|
#define SYSCONFIG1_SMUTE (0x1 << 9)
|
|
|
|
|
|
|
|
/* SYSCONFIG2 (0x5) */
|
|
|
|
#define SYSCONFIG2_VOLUME (0xF << 0)
|
2010-06-15 20:57:48 +00:00
|
|
|
|
|
|
|
/* READCHAN (0xA) */
|
|
|
|
#define READCHAN_READCHAN (0x3ff << 0)
|
|
|
|
#define READCHAN_READCHANr(x) (((x) & READCHAN_READCHAN) >> 0)
|
|
|
|
#define READCHAN_STC (0x1 << 14)
|
2010-06-16 06:53:14 +00:00
|
|
|
#define READCHAN_ST (0x1 << 10)
|
2010-06-15 20:57:48 +00:00
|
|
|
|
|
|
|
/* STATUSRSSI (0xB) */
|
2010-06-16 20:29:08 +00:00
|
|
|
#define STATUSRSSI_RSSI (0x7F << 9)
|
|
|
|
#define STATUSRSSI_RSSIr(x) (((x) & STATUSRSSI_RSSI) >> 9)
|
|
|
|
#define STATUSRSSI_FM_TRUE (0x1 << 8)
|
2010-06-15 20:57:48 +00:00
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
static const uint16_t initvals[16] = {
|
|
|
|
0x0000, 0x0000, 0xC401, 0x1B90,
|
2010-06-15 20:57:48 +00:00
|
|
|
0x0400, 0x866F, 0x8000, 0x4712,
|
|
|
|
0x5EC6, 0x0000, 0x406E, 0x2D80,
|
2010-06-16 20:29:08 +00:00
|
|
|
0x5803, 0x5804, 0x5804, 0x5804
|
2010-06-15 20:57:48 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static bool tuner_present = false;
|
|
|
|
static int curr_frequency = 87500000; /* Current station frequency (HZ) */
|
2010-06-16 20:29:08 +00:00
|
|
|
static uint16_t cache[16];
|
2010-06-15 20:57:48 +00:00
|
|
|
|
|
|
|
/* reads <len> registers from radio at offset 0x0A into cache */
|
2010-06-16 20:29:08 +00:00
|
|
|
static void rda5802_read(int len)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
|
|
|
int i;
|
2010-06-16 20:29:08 +00:00
|
|
|
unsigned char buf[128];
|
2010-06-15 20:57:48 +00:00
|
|
|
unsigned char *ptr = buf;
|
|
|
|
uint16_t data;
|
|
|
|
|
|
|
|
fmradio_i2c_read(I2C_ADR, buf, len * 2);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
data = ptr[0] << 8 | ptr[1];
|
2010-06-16 20:29:08 +00:00
|
|
|
cache[READCHAN + i] = data;
|
2010-06-15 20:57:48 +00:00
|
|
|
ptr += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* writes <len> registers from cache to radio at offset 0x02 */
|
2010-06-16 20:29:08 +00:00
|
|
|
static void rda5802_write(int len)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
unsigned char buf[64];
|
|
|
|
unsigned char *ptr = buf;
|
|
|
|
uint16_t data;
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++) {
|
2010-06-16 20:29:08 +00:00
|
|
|
data = cache[POWERCFG + i];
|
2010-06-15 20:57:48 +00:00
|
|
|
*ptr++ = (data >> 8) & 0xFF;
|
|
|
|
*ptr++ = data & 0xFF;
|
|
|
|
}
|
|
|
|
fmradio_i2c_write(I2C_ADR, buf, len * 2);
|
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
static uint16_t rda5802_read_reg(int reg)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_read((reg - READCHAN) + 1);
|
2010-06-15 20:57:48 +00:00
|
|
|
return cache[reg];
|
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
static void rda5802_write_reg(int reg, uint16_t value)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
|
|
|
cache[reg] = value;
|
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
static void rda5802_write_cache(void)
|
|
|
|
{
|
|
|
|
rda5802_write(5);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void rda5802_write_masked(int reg, uint16_t bits, uint16_t mask)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_reg(reg, (cache[reg] & ~mask) | (bits & mask));
|
2010-06-15 20:57:48 +00:00
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
static void rda5802_write_clear(int reg, uint16_t mask)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_reg(reg, cache[reg] & ~mask);
|
2010-06-15 20:57:48 +00:00
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
static void rda5802_write_set(int reg, uint16_t mask)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_reg(reg, cache[reg] | mask);
|
2010-06-15 20:57:48 +00:00
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
static void rda5802_sleep(int snooze)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
|
|
|
if (snooze) {
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_clear(POWERCFG, POWERCFG_ENABLE);
|
2010-06-15 20:57:48 +00:00
|
|
|
}
|
|
|
|
else {
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_set(POWERCFG, POWERCFG_ENABLE);
|
2010-06-15 20:57:48 +00:00
|
|
|
}
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_cache();
|
2010-06-15 20:57:48 +00:00
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
bool rda5802_detect(void)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
2010-06-16 20:29:08 +00:00
|
|
|
return ((rda5802_read_reg(IDENT) & 0xFF00) == 0x5800);
|
2010-06-15 20:57:48 +00:00
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
void rda5802_init(void)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
2010-06-16 20:29:08 +00:00
|
|
|
if (rda5802_detect()) {
|
2010-06-15 20:57:48 +00:00
|
|
|
tuner_present = true;
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
// soft-reset
|
|
|
|
rda5802_write_reg(POWERCFG, POWERCFG_SOFT_RESET);
|
|
|
|
rda5802_write(1);
|
|
|
|
sleep(HZ * 10 / 1000);
|
2010-06-15 20:57:48 +00:00
|
|
|
|
|
|
|
// write initialisation values
|
|
|
|
memcpy(cache, initvals, sizeof(cache));
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write(16);
|
2010-06-15 20:57:48 +00:00
|
|
|
sleep(HZ * 70 / 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
static void rda5802_set_frequency(int freq)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
|
|
|
int i;
|
2010-06-16 20:29:08 +00:00
|
|
|
uint16_t readchan;
|
2010-06-15 20:57:48 +00:00
|
|
|
|
|
|
|
/* check BAND and spacings */
|
|
|
|
int start = CHANNEL_BANDr(cache[CHANNEL]) & 1 ? 76000000 : 87000000;
|
|
|
|
int chan = (freq - start) / 50000;
|
|
|
|
|
|
|
|
curr_frequency = freq;
|
|
|
|
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
|
|
/* tune and wait a bit */
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_masked(CHANNEL, CHANNEL_CHANw(chan) | CHANNEL_TUNE,
|
2010-06-15 20:57:48 +00:00
|
|
|
CHANNEL_CHAN | CHANNEL_TUNE);
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_cache();
|
2010-06-15 20:57:48 +00:00
|
|
|
sleep(HZ * 70 / 1000);
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_clear(CHANNEL, CHANNEL_TUNE);
|
|
|
|
rda5802_write_cache();
|
2010-06-15 20:57:48 +00:00
|
|
|
|
|
|
|
/* check if tuning was successful */
|
2010-06-16 20:29:08 +00:00
|
|
|
readchan = rda5802_read_reg(READCHAN);
|
|
|
|
if (readchan & READCHAN_STC) {
|
|
|
|
if (READCHAN_READCHANr(readchan) == chan) {
|
2010-06-15 20:57:48 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
static int rda5802_tuned(void)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
|
|
|
/* Primitive tuning check: sufficient level and AFC not railed */
|
2010-06-16 20:29:08 +00:00
|
|
|
uint16_t status = rda5802_read_reg(STATUSRSSI);
|
2010-06-15 20:57:48 +00:00
|
|
|
if (STATUSRSSI_RSSIr(status) >= SEEK_THRESHOLD &&
|
2010-06-16 20:29:08 +00:00
|
|
|
(status & STATUSRSSI_FM_TRUE)) {
|
2010-06-15 20:57:48 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
static void rda5802_set_region(int region)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
2010-07-26 20:15:16 +00:00
|
|
|
const struct fm_region_data *rd = &fm_region_data[region];
|
|
|
|
int band = (rd->freq_min == 76000000) ?
|
|
|
|
CHANNEL_BAND_760_900 : CHANNEL_BAND_870_1080;
|
|
|
|
int deemphasis = (rd->deemphasis == 50) ? SYSCONFIG1_DE : 0;
|
|
|
|
|
|
|
|
uint16_t bandspacing = CHANNEL_BANDw(band) |
|
2010-06-15 20:57:48 +00:00
|
|
|
CHANNEL_SPACEw(CHANNEL_SPACE_50KHZ);
|
|
|
|
uint16_t oldbs = cache[CHANNEL] & (CHANNEL_BAND | CHANNEL_SPACE);
|
|
|
|
|
2010-07-26 20:15:16 +00:00
|
|
|
rda5802_write_masked(SYSCONFIG1, deemphasis, SYSCONFIG1_DE);
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_masked(CHANNEL, bandspacing, CHANNEL_BAND | CHANNEL_SPACE);
|
|
|
|
rda5802_write_cache();
|
2010-06-15 20:57:48 +00:00
|
|
|
|
|
|
|
/* Retune if this region change would change the channel number. */
|
|
|
|
if (oldbs != bandspacing) {
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_set_frequency(curr_frequency);
|
2010-06-15 20:57:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
static bool rda5802_st(void)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
2010-06-16 20:29:08 +00:00
|
|
|
return (rda5802_read_reg(READCHAN) & READCHAN_ST);
|
2010-06-15 20:57:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* tuner abstraction layer: set something to the tuner */
|
2010-06-16 20:29:08 +00:00
|
|
|
int rda5802_set(int setting, int value)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
|
|
|
switch (setting) {
|
|
|
|
case RADIO_SLEEP:
|
|
|
|
if (value != 2) {
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_sleep(value);
|
2010-06-15 20:57:48 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RADIO_FREQUENCY:
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_set_frequency(value);
|
2010-06-15 20:57:48 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case RADIO_SCAN_FREQUENCY:
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_set_frequency(value);
|
|
|
|
return rda5802_tuned();
|
2010-06-15 20:57:48 +00:00
|
|
|
|
|
|
|
case RADIO_MUTE:
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_masked(POWERCFG, value ? 0 : POWERCFG_DMUTE,
|
2010-06-15 20:57:48 +00:00
|
|
|
POWERCFG_DMUTE);
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_masked(SYSCONFIG1, (3 << 9), (3 << 9));
|
|
|
|
rda5802_write_set(SYSCONFIG2, SYSCONFIG2_VOLUME);
|
|
|
|
rda5802_write_cache();
|
2010-06-15 20:57:48 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case RADIO_REGION:
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_set_region(value);
|
2010-06-15 20:57:48 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case RADIO_FORCE_MONO:
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_masked(POWERCFG, value ? POWERCFG_MONO : 0,
|
2010-06-15 20:57:48 +00:00
|
|
|
POWERCFG_MONO);
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_write_cache();
|
2010-06-15 20:57:48 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* tuner abstraction layer: read something from the tuner */
|
2010-06-16 20:29:08 +00:00
|
|
|
int rda5802_get(int setting)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
|
|
|
int val = -1; /* default for unsupported query */
|
|
|
|
|
|
|
|
switch (setting) {
|
|
|
|
case RADIO_PRESENT:
|
|
|
|
val = tuner_present ? 1 : 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RADIO_TUNED:
|
2010-06-16 20:29:08 +00:00
|
|
|
val = rda5802_tuned();
|
2010-06-15 20:57:48 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case RADIO_STEREO:
|
2010-06-16 20:29:08 +00:00
|
|
|
val = rda5802_st();
|
2010-06-15 20:57:48 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2010-06-16 20:29:08 +00:00
|
|
|
void rda5802_dbg_info(struct rda5802_dbg_info *nfo)
|
2010-06-15 20:57:48 +00:00
|
|
|
{
|
2010-06-16 20:29:08 +00:00
|
|
|
rda5802_read(6);
|
2010-06-15 20:57:48 +00:00
|
|
|
memcpy(nfo->regs, cache, sizeof (nfo->regs));
|
|
|
|
}
|
|
|
|
|