summaryrefslogtreecommitdiff
path: root/firmware/drivers/tuner/rda5802.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/drivers/tuner/rda5802.c')
-rw-r--r--firmware/drivers/tuner/rda5802.c333
1 files changed, 333 insertions, 0 deletions
diff --git a/firmware/drivers/tuner/rda5802.c b/firmware/drivers/tuner/rda5802.c
new file mode 100644
index 0000000000..a505b6b382
--- /dev/null
+++ b/firmware/drivers/tuner/rda5802.c
@@ -0,0 +1,333 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Tuner "middleware" for RDA5802 chip present in some Sansa Clip+ players
+ *
+ * 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 */
+
+#define SEEK_THRESHOLD 0x16
+
+#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)
+#define POWERCFG_SOFT_RESET (0x1 << 1)
+#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)
+ #define CHANNEL_BAND_875_1080 (0x0 << 2) /* tenth-megahertz */
+ #define CHANNEL_BAND_760_1080 (0x1 << 2)
+ #define CHANNEL_BAND_760_900 (0x2 << 2)
+ #define CHANNEL_BAND_650_760 (0x3 << 2)
+#define CHANNEL_SPACE (0x3 << 0)
+ #define CHANNEL_SPACEw(x) (((x) << 0) & CHANNEL_SPACE)
+ #define CHANNEL_SPACEr(x) (((x) & CHANNEL_SPACE) >> 0)
+ #define CHANNEL_SPACE_100KHZ (0x0 << 0)
+ #define CHANNEL_SPACE_200KHZ (0x1 << 0)
+ #define CHANNEL_SPACE_50KHZ (0x2 << 0)
+
+/* SYSCONFIG1 (0x4) */
+#define SYSCONFIG1_DE (0x1 << 11)
+ #define SYSCONFIG1_SMUTE (0x1 << 9)
+
+/* SYSCONFIG2 (0x5) */
+#define SYSCONFIG2_VOLUME (0xF << 0)
+
+/* READCHAN (0xA) */
+#define READCHAN_READCHAN (0x3ff << 0)
+ #define READCHAN_READCHANr(x) (((x) & READCHAN_READCHAN) >> 0)
+#define READCHAN_STC (0x1 << 14)
+#define READCHAN_ST (0x1 << 10)
+
+/* STATUSRSSI (0xB) */
+#define STATUSRSSI_RSSI (0x7F << 9)
+ #define STATUSRSSI_RSSIr(x) (((x) & STATUSRSSI_RSSI) >> 9)
+#define STATUSRSSI_FM_TRUE (0x1 << 8)
+
+static const uint16_t initvals[16] = {
+ 0x0000, 0x0000, 0xC401, 0x1B90,
+ 0x0400, 0x866F, 0x8000, 0x4712,
+ 0x5EC6, 0x0000, 0x406E, 0x2D80,
+ 0x5803, 0x5804, 0x5804, 0x5804
+};
+
+static bool tuner_present = false;
+static int curr_frequency = 87500000; /* Current station frequency (HZ) */
+static uint16_t cache[16];
+
+/* reads <len> registers from radio at offset 0x0A into cache */
+static void rda5802_read(int len)
+{
+ int i;
+ unsigned char buf[128];
+ 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];
+ cache[READCHAN + i] = data;
+ ptr += 2;
+ }
+}
+
+/* writes <len> registers from cache to radio at offset 0x02 */
+static void rda5802_write(int len)
+{
+ int i;
+ unsigned char buf[64];
+ unsigned char *ptr = buf;
+ uint16_t data;
+
+ for (i = 0; i < len; i++) {
+ data = cache[POWERCFG + i];
+ *ptr++ = (data >> 8) & 0xFF;
+ *ptr++ = data & 0xFF;
+ }
+ fmradio_i2c_write(I2C_ADR, buf, len * 2);
+}
+
+static uint16_t rda5802_read_reg(int reg)
+{
+ rda5802_read((reg - READCHAN) + 1);
+ return cache[reg];
+}
+
+static void rda5802_write_reg(int reg, uint16_t value)
+{
+ cache[reg] = value;
+}
+
+static void rda5802_write_cache(void)
+{
+ rda5802_write(5);
+}
+
+static void rda5802_write_masked(int reg, uint16_t bits, uint16_t mask)
+{
+ rda5802_write_reg(reg, (cache[reg] & ~mask) | (bits & mask));
+}
+
+static void rda5802_write_clear(int reg, uint16_t mask)
+{
+ rda5802_write_reg(reg, cache[reg] & ~mask);
+}
+
+static void rda5802_write_set(int reg, uint16_t mask)
+{
+ rda5802_write_reg(reg, cache[reg] | mask);
+}
+
+static void rda5802_sleep(int snooze)
+{
+ if (snooze) {
+ rda5802_write_clear(POWERCFG, POWERCFG_ENABLE);
+ }
+ else {
+ rda5802_write_set(POWERCFG, POWERCFG_ENABLE);
+ }
+ rda5802_write_cache();
+}
+
+bool rda5802_detect(void)
+{
+ return ((rda5802_read_reg(IDENT) & 0xFF00) == 0x5800);
+ }
+
+void rda5802_init(void)
+{
+ if (rda5802_detect()) {
+ tuner_present = true;
+
+ // soft-reset
+ rda5802_write_reg(POWERCFG, POWERCFG_SOFT_RESET);
+ rda5802_write(1);
+ sleep(HZ * 10 / 1000);
+
+ // write initialisation values
+ memcpy(cache, initvals, sizeof(cache));
+ rda5802_write(16);
+ sleep(HZ * 70 / 1000);
+ }
+}
+
+static void rda5802_set_frequency(int freq)
+{
+ int i;
+ uint16_t readchan;
+
+ /* 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 */
+ rda5802_write_masked(CHANNEL, CHANNEL_CHANw(chan) | CHANNEL_TUNE,
+ CHANNEL_CHAN | CHANNEL_TUNE);
+ rda5802_write_cache();
+ sleep(HZ * 70 / 1000);
+ rda5802_write_clear(CHANNEL, CHANNEL_TUNE);
+ rda5802_write_cache();
+
+ /* check if tuning was successful */
+ readchan = rda5802_read_reg(READCHAN);
+ if (readchan & READCHAN_STC) {
+ if (READCHAN_READCHANr(readchan) == chan) {
+ break;
+ }
+ }
+ }
+}
+
+static int rda5802_tuned(void)
+{
+ /* Primitive tuning check: sufficient level and AFC not railed */
+ uint16_t status = rda5802_read_reg(STATUSRSSI);
+ if (STATUSRSSI_RSSIr(status) >= SEEK_THRESHOLD &&
+ (status & STATUSRSSI_FM_TRUE)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static void rda5802_set_region(int region)
+{
+ const struct rda5802_region_data *rd = &rda5802_region_data[region];
+ uint16_t bandspacing = CHANNEL_BANDw(rd->band) |
+ CHANNEL_SPACEw(CHANNEL_SPACE_50KHZ);
+ uint16_t oldbs = cache[CHANNEL] & (CHANNEL_BAND | CHANNEL_SPACE);
+
+ rda5802_write_masked(SYSCONFIG1, rd->deemphasis ? SYSCONFIG1_DE : 0,
+ SYSCONFIG1_DE);
+ rda5802_write_masked(CHANNEL, bandspacing, CHANNEL_BAND | CHANNEL_SPACE);
+ rda5802_write_cache();
+
+ /* Retune if this region change would change the channel number. */
+ if (oldbs != bandspacing) {
+ rda5802_set_frequency(curr_frequency);
+ }
+}
+
+static bool rda5802_st(void)
+{
+ return (rda5802_read_reg(READCHAN) & READCHAN_ST);
+}
+
+/* tuner abstraction layer: set something to the tuner */
+int rda5802_set(int setting, int value)
+{
+ switch (setting) {
+ case RADIO_SLEEP:
+ if (value != 2) {
+ rda5802_sleep(value);
+ }
+ break;
+
+ case RADIO_FREQUENCY:
+ rda5802_set_frequency(value);
+ break;
+
+ case RADIO_SCAN_FREQUENCY:
+ rda5802_set_frequency(value);
+ return rda5802_tuned();
+
+ case RADIO_MUTE:
+ rda5802_write_masked(POWERCFG, value ? 0 : POWERCFG_DMUTE,
+ POWERCFG_DMUTE);
+ rda5802_write_masked(SYSCONFIG1, (3 << 9), (3 << 9));
+ rda5802_write_set(SYSCONFIG2, SYSCONFIG2_VOLUME);
+ rda5802_write_cache();
+ break;
+
+ case RADIO_REGION:
+ rda5802_set_region(value);
+ break;
+
+ case RADIO_FORCE_MONO:
+ rda5802_write_masked(POWERCFG, value ? POWERCFG_MONO : 0,
+ POWERCFG_MONO);
+ rda5802_write_cache();
+ break;
+
+ default:
+ return -1;
+ }
+
+ return 1;
+}
+
+/* tuner abstraction layer: read something from the tuner */
+int rda5802_get(int setting)
+{
+ int val = -1; /* default for unsupported query */
+
+ switch (setting) {
+ case RADIO_PRESENT:
+ val = tuner_present ? 1 : 0;
+ break;
+
+ case RADIO_TUNED:
+ val = rda5802_tuned();
+ break;
+
+ case RADIO_STEREO:
+ val = rda5802_st();
+ break;
+ }
+
+ return val;
+}
+
+void rda5802_dbg_info(struct rda5802_dbg_info *nfo)
+{
+ rda5802_read(6);
+ memcpy(nfo->regs, cache, sizeof (nfo->regs));
+}
+