diff options
Diffstat (limited to 'firmware/drivers/tuner/fmclipplus.c')
-rw-r--r-- | firmware/drivers/tuner/fmclipplus.c | 328 |
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 | |||
85 | static 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 | |||
97 | static bool tuner_present = false; | ||
98 | static int curr_frequency = 87500000; /* Current station frequency (HZ) */ | ||
99 | static uint16_t cache[32]; | ||
100 | |||
101 | /* reads <len> registers from radio at offset 0x0A into cache */ | ||
102 | static 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 */ | ||
118 | static 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 | |||
133 | static uint16_t fmclipplus_read_reg(int reg) | ||
134 | { | ||
135 | fmclipplus_read(((reg - READCHAN) & 0x1F) + 1); | ||
136 | return cache[reg]; | ||
137 | } | ||
138 | |||
139 | static void fmclipplus_write_reg(int reg, uint16_t value) | ||
140 | { | ||
141 | cache[reg] = value; | ||
142 | } | ||
143 | |||
144 | static void fmclipplus_write_cache(void) | ||
145 | { | ||
146 | fmclipplus_write(5); | ||
147 | } | ||
148 | |||
149 | static 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 | |||
154 | static void fmclipplus_write_clear(int reg, uint16_t mask) | ||
155 | { | ||
156 | fmclipplus_write_reg(reg, cache[reg] & ~mask); | ||
157 | } | ||
158 | |||
159 | static 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 | |||
170 | bool fmclipplus_detect(void) | ||
171 | { | ||
172 | return ((fmclipplus_read_reg(IDENT) & 0xFF00) == 0x5800); | ||
173 | } | ||
174 | |||
175 | void 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 | |||
192 | static 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 | |||
222 | static 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 | |||
234 | static 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 | |||
252 | static bool fmclipplus_st(void) | ||
253 | { | ||
254 | /* TODO not implemented yet */ | ||
255 | return false; | ||
256 | } | ||
257 | |||
258 | /* tuner abstraction layer: set something to the tuner */ | ||
259 | int 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 */ | ||
302 | int 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 | |||
323 | void fmclipplus_dbg_info(struct fmclipplus_dbg_info *nfo) | ||
324 | { | ||
325 | fmclipplus_read(32); | ||
326 | memcpy(nfo->regs, cache, sizeof (nfo->regs)); | ||
327 | } | ||
328 | |||