summaryrefslogtreecommitdiff
path: root/firmware/drivers/tuner/fmclipplus.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/drivers/tuner/fmclipplus.c')
-rw-r--r--firmware/drivers/tuner/fmclipplus.c328
1 files changed, 328 insertions, 0 deletions
diff --git a/firmware/drivers/tuner/fmclipplus.c b/firmware/drivers/tuner/fmclipplus.c
new file mode 100644
index 0000000000..4badfba08f
--- /dev/null
+++ b/firmware/drivers/tuner/fmclipplus.c
@@ -0,0 +1,328 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Tuner "middleware" for unidentified Silicon Labs chip present in some
11 * Sansa Clip+ players
12 *
13 * Copyright (C) 2010 Bertrik Sikken
14 * Copyright (C) 2008 Nils Wallménius (si4700 code that this was based on)
15 *
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation; either version 2
19 * of the License, or (at your option) any later version.
20 *
21 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
22 * KIND, either express or implied.
23 *
24 ****************************************************************************/
25#include "config.h"
26#include <stdbool.h>
27#include <string.h>
28#include <stdlib.h>
29#include "kernel.h"
30#include "tuner.h" /* tuner abstraction interface */
31#include "fmradio.h"
32#include "fmradio_i2c.h" /* physical interface driver */
33
34#define SEEK_THRESHOLD 0x10
35
36#define I2C_ADR 0x20
37
38/** Registers and bits **/
39#define POWERCFG 0x2
40#define CHANNEL 0x3
41#define SYSCONFIG1 0x4
42#define SYSCONFIG2 0x5
43#define SYSCONFIG3 0x6
44
45#define READCHAN 0xA
46#define STATUSRSSI 0xB
47#define IDENT 0xC
48
49
50/* POWERCFG (0x2) */
51#define POWERCFG_DMUTE (0x1 << 14)
52#define POWERCFG_MONO (0x1 << 13)
53#define POWERCFG_ENABLE (0x1 << 0)
54
55/* CHANNEL (0x3) */
56#define CHANNEL_CHAN (0x3ff << 6)
57 #define CHANNEL_CHANw(x) (((x) << 6) & CHANNEL_CHAN)
58#define CHANNEL_TUNE (0x1 << 4)
59#define CHANNEL_BAND (0x3 << 2)
60 #define CHANNEL_BANDw(x) (((x) << 2) & CHANNEL_BAND)
61 #define CHANNEL_BANDr(x) (((x) & CHANNEL_BAND) >> 2)
62 #define CHANNEL_BAND_875_1080 (0x0 << 2) /* tenth-megahertz */
63 #define CHANNEL_BAND_760_1080 (0x1 << 2)
64 #define CHANNEL_BAND_760_900 (0x2 << 2)
65#define CHANNEL_SPACE (0x3 << 0)
66 #define CHANNEL_SPACEw(x) (((x) << 0) & CHANNEL_SPACE)
67 #define CHANNEL_SPACEr(x) (((x) & CHANNEL_SPACE) >> 0)
68 #define CHANNEL_SPACE_200KHZ (0x0 << 0)
69 #define CHANNEL_SPACE_100KHZ (0x1 << 0)
70 #define CHANNEL_SPACE_50KHZ (0x2 << 0)
71
72/* SYSCONFIG1 (0x4) */
73#define SYSCONFIG1_DE (0x1 << 11)
74
75/* READCHAN (0xA) */
76#define READCHAN_READCHAN (0x3ff << 0)
77 #define READCHAN_READCHANr(x) (((x) & READCHAN_READCHAN) >> 0)
78#define READCHAN_STC (0x1 << 14)
79
80/* STATUSRSSI (0xB) */
81#define STATUSRSSI_RSSI (0x3F << 10)
82 #define STATUSRSSI_RSSIr(x) (((x) & STATUSRSSI_RSSI) >> 10)
83#define STATUSRSSI_AFCRL (0x1 << 8)
84
85static const uint16_t initvals[32] = {
86 0x8110, 0x4580, 0xC401, 0x1B90,
87 0x0400, 0x866F, 0x8000, 0x4712,
88 0x5EC6, 0x0000, 0x406E, 0x2D80,
89 0x5803, 0x5804, 0x5804, 0x5804,
90
91 0x0047, 0x9000, 0xF587, 0x0009,
92 0x00F1, 0x41C0, 0x41E0, 0x506F,
93 0x5592, 0x007D, 0x10A0, 0x0780,
94 0x311D, 0x4006, 0x1F9B, 0x4C2B
95};
96
97static bool tuner_present = false;
98static int curr_frequency = 87500000; /* Current station frequency (HZ) */
99static uint16_t cache[32];
100
101/* reads <len> registers from radio at offset 0x0A into cache */
102static void fmclipplus_read(int len)
103{
104 int i;
105 unsigned char buf[64];
106 unsigned char *ptr = buf;
107 uint16_t data;
108
109 fmradio_i2c_read(I2C_ADR, buf, len * 2);
110 for (i = 0; i < len; i++) {
111 data = ptr[0] << 8 | ptr[1];
112 cache[(i + READCHAN) & 0x1F] = data;
113 ptr += 2;
114 }
115}
116
117/* writes <len> registers from cache to radio at offset 0x02 */
118static void fmclipplus_write(int len)
119{
120 int i;
121 unsigned char buf[64];
122 unsigned char *ptr = buf;
123 uint16_t data;
124
125 for (i = 0; i < len; i++) {
126 data = cache[(i + POWERCFG) & 0x1F];
127 *ptr++ = (data >> 8) & 0xFF;
128 *ptr++ = data & 0xFF;
129 }
130 fmradio_i2c_write(I2C_ADR, buf, len * 2);
131}
132
133static uint16_t fmclipplus_read_reg(int reg)
134{
135 fmclipplus_read(((reg - READCHAN) & 0x1F) + 1);
136 return cache[reg];
137}
138
139static void fmclipplus_write_reg(int reg, uint16_t value)
140{
141 cache[reg] = value;
142}
143
144static void fmclipplus_write_cache(void)
145{
146 fmclipplus_write(5);
147}
148
149static void fmclipplus_write_masked(int reg, uint16_t bits, uint16_t mask)
150{
151 fmclipplus_write_reg(reg, (cache[reg] & ~mask) | (bits & mask));
152}
153
154static void fmclipplus_write_clear(int reg, uint16_t mask)
155{
156 fmclipplus_write_reg(reg, cache[reg] & ~mask);
157}
158
159static void fmclipplus_sleep(int snooze)
160{
161 if (snooze) {
162 fmclipplus_write_masked(POWERCFG, 0, 0xFF);
163 }
164 else {
165 fmclipplus_write_masked(POWERCFG, 1, 0xFF);
166 }
167 fmclipplus_write_cache();
168}
169
170bool fmclipplus_detect(void)
171{
172 return ((fmclipplus_read_reg(IDENT) & 0xFF00) == 0x5800);
173 }
174
175void fmclipplus_init(void)
176{
177 if (fmclipplus_detect()) {
178 tuner_present = true;
179
180 // send pre-initialisation value
181 fmclipplus_write_reg(POWERCFG, 0x200);
182 fmclipplus_write(2);
183 sleep(HZ * 10 / 100);
184
185 // write initialisation values
186 memcpy(cache, initvals, sizeof(cache));
187 fmclipplus_write(32);
188 sleep(HZ * 70 / 1000);
189 }
190}
191
192static void fmclipplus_set_frequency(int freq)
193{
194 int i;
195
196 /* check BAND and spacings */
197 fmclipplus_read_reg(STATUSRSSI);
198 int start = CHANNEL_BANDr(cache[CHANNEL]) & 1 ? 76000000 : 87000000;
199 int chan = (freq - start) / 50000;
200
201 curr_frequency = freq;
202
203 for (i = 0; i < 5; i++) {
204 /* tune and wait a bit */
205 fmclipplus_write_masked(CHANNEL, CHANNEL_CHANw(chan) | CHANNEL_TUNE,
206 CHANNEL_CHAN | CHANNEL_TUNE);
207 fmclipplus_write_cache();
208 sleep(HZ * 70 / 1000);
209 fmclipplus_write_clear(CHANNEL, CHANNEL_TUNE);
210 fmclipplus_write_cache();
211
212 /* check if tuning was successful */
213 fmclipplus_read_reg(STATUSRSSI);
214 if (cache[READCHAN] & READCHAN_STC) {
215 if (READCHAN_READCHANr(cache[READCHAN]) == chan) {
216 break;
217 }
218 }
219 }
220}
221
222static int fmclipplus_tuned(void)
223{
224 /* Primitive tuning check: sufficient level and AFC not railed */
225 uint16_t status = fmclipplus_read_reg(STATUSRSSI);
226 if (STATUSRSSI_RSSIr(status) >= SEEK_THRESHOLD &&
227 (status & STATUSRSSI_AFCRL) == 0) {
228 return 1;
229 }
230
231 return 0;
232}
233
234static void fmclipplus_set_region(int region)
235{
236 const struct fmclipplus_region_data *rd = &fmclipplus_region_data[region];
237 uint16_t bandspacing = CHANNEL_BANDw(rd->band) |
238 CHANNEL_SPACEw(CHANNEL_SPACE_50KHZ);
239 uint16_t oldbs = cache[CHANNEL] & (CHANNEL_BAND | CHANNEL_SPACE);
240
241 fmclipplus_write_masked(SYSCONFIG1, rd->deemphasis ? SYSCONFIG1_DE : 0,
242 SYSCONFIG1_DE);
243 fmclipplus_write_masked(CHANNEL, bandspacing, CHANNEL_BAND | CHANNEL_SPACE);
244 fmclipplus_write_cache();
245
246 /* Retune if this region change would change the channel number. */
247 if (oldbs != bandspacing) {
248 fmclipplus_set_frequency(curr_frequency);
249 }
250}
251
252static bool fmclipplus_st(void)
253{
254 /* TODO not implemented yet */
255 return false;
256}
257
258/* tuner abstraction layer: set something to the tuner */
259int fmclipplus_set(int setting, int value)
260{
261 switch (setting) {
262 case RADIO_SLEEP:
263 if (value != 2) {
264 fmclipplus_sleep(value);
265 }
266 break;
267
268 case RADIO_FREQUENCY:
269 fmclipplus_set_frequency(value);
270 break;
271
272 case RADIO_SCAN_FREQUENCY:
273 fmclipplus_set_frequency(value);
274 return fmclipplus_tuned();
275
276 case RADIO_MUTE:
277 fmclipplus_write_masked(POWERCFG, value ? 0 : POWERCFG_DMUTE,
278 POWERCFG_DMUTE);
279 fmclipplus_write_masked(SYSCONFIG1, (3 << 9), (3 << 9));
280 fmclipplus_write_masked(SYSCONFIG2, (0xF << 0), (0xF << 0));
281 fmclipplus_write_cache();
282 break;
283
284 case RADIO_REGION:
285 fmclipplus_set_region(value);
286 break;
287
288 case RADIO_FORCE_MONO:
289 fmclipplus_write_masked(POWERCFG, value ? POWERCFG_MONO : 0,
290 POWERCFG_MONO);
291 fmclipplus_write_cache();
292 break;
293
294 default:
295 return -1;
296 }
297
298 return 1;
299}
300
301/* tuner abstraction layer: read something from the tuner */
302int fmclipplus_get(int setting)
303{
304 int val = -1; /* default for unsupported query */
305
306 switch (setting) {
307 case RADIO_PRESENT:
308 val = tuner_present ? 1 : 0;
309 break;
310
311 case RADIO_TUNED:
312 val = fmclipplus_tuned();
313 break;
314
315 case RADIO_STEREO:
316 val = fmclipplus_st();
317 break;
318 }
319
320 return val;
321}
322
323void fmclipplus_dbg_info(struct fmclipplus_dbg_info *nfo)
324{
325 fmclipplus_read(32);
326 memcpy(nfo->regs, cache, sizeof (nfo->regs));
327}
328