summaryrefslogtreecommitdiff
path: root/firmware/drivers
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2007-07-14 11:20:31 +0000
committerMichael Sevakis <jethead71@rockbox.org>2007-07-14 11:20:31 +0000
commit7d759f6b9ca96a4a64c71ac301eb59cb9702e74c (patch)
treefbaf5104716ccfed888c7d8506dbee15a881d4a2 /firmware/drivers
parentb51a20fb9b1cc57b813d1c0b90ad8c010b9eab84 (diff)
downloadrockbox-7d759f6b9ca96a4a64c71ac301eb59cb9702e74c.tar.gz
rockbox-7d759f6b9ca96a4a64c71ac301eb59cb9702e74c.zip
Do some planned radio interface cleanup since adding in the LV24020LP.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13880 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware/drivers')
-rw-r--r--firmware/drivers/power.c4
-rw-r--r--firmware/drivers/tuner/lv24020lp.c870
-rw-r--r--firmware/drivers/tuner/s1a0903x01.c172
-rw-r--r--firmware/drivers/tuner/tea5767.c135
4 files changed, 1179 insertions, 2 deletions
diff --git a/firmware/drivers/power.c b/firmware/drivers/power.c
index eb69fcec27..50117dd321 100644
--- a/firmware/drivers/power.c
+++ b/firmware/drivers/power.c
@@ -37,12 +37,12 @@ bool charger_enabled;
37 37
38static bool powered = false; 38static bool powered = false;
39 39
40bool radio_powered(void) 40bool tuner_powered(void)
41{ 41{
42 return powered; 42 return powered;
43} 43}
44 44
45bool radio_power(bool status) 45bool tuner_power(bool status)
46{ 46{
47 bool old_status = powered; 47 bool old_status = powered;
48 powered = status; 48 powered = status;
diff --git a/firmware/drivers/tuner/lv24020lp.c b/firmware/drivers/tuner/lv24020lp.c
new file mode 100644
index 0000000000..9ec68f9a27
--- /dev/null
+++ b/firmware/drivers/tuner/lv24020lp.c
@@ -0,0 +1,870 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 * Tuner driver for the Sanyo LV24020LP
10 *
11 * Copyright (C) 2007 Ivan Zupan
12 *
13 * All files in this archive are subject to the GNU General Public License.
14 * See the file COPYING in the source tree root for full license agreement.
15 *
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
18 *
19 ****************************************************************************/
20
21#include <stdbool.h>
22#include <stdlib.h>
23#include "config.h"
24#include "thread.h"
25#include "kernel.h"
26#include "tuner.h" /* tuner abstraction interface */
27#include "fmradio.h" /* physical interface driver */
28#include "sound.h"
29#include "pp5024.h"
30#include "system.h"
31
32#ifndef BOOTLOADER
33
34#if 0
35/* define to enable tuner logging */
36#define SANYO_TUNER_LOG
37#endif
38
39#ifdef SANYO_TUNER_LOG
40#include "sprintf.h"
41#include "file.h"
42
43static int fd_log = -1;
44
45#define TUNER_LOG_OPEN() if (fd_log < 0) \
46 fd_log = creat("/tuner_dump.txt")
47/* syncing required because close() is never called */
48#define TUNER_LOG_SYNC() fsync(fd_log)
49#define TUNER_LOG(s...) fdprintf(fd_log, s)
50#else
51#define TUNER_LOG_OPEN()
52#define TUNER_LOG_SYNC()
53#define TUNER_LOG(s...)
54#endif /* SANYO_TUNER_LOG */
55
56/** tuner register defines **/
57
58/* pins on GPIOH port */
59#define FM_NRW_PIN 3
60#define FM_CLOCK_PIN 4
61#define FM_DATA_PIN 5
62#define FM_CLK_DELAY 1
63
64/* block 1 registers */
65
66/* R */
67#define CHIP_ID 0x00
68
69/* W */
70#define BLK_SEL 0x01
71 #define BLK1 0x01
72 #define BLK2 0x02
73
74/* W */
75#define MSRC_SEL 0x02
76 #define MSR_O (1 << 7)
77 #define AFC_LVL (1 << 6)
78 #define AFC_SPD (1 << 5)
79 #define MSS_SD (1 << 2)
80 #define MSS_FM (1 << 1)
81 #define MSS_IF (1 << 0)
82
83/* W */
84#define FM_OSC 0x03
85
86/* W */
87#define SD_OSC 0x04
88
89/* W */
90#define IF_OSC 0x05
91
92/* W */
93#define CNT_CTRL 0x06
94 #define CNT1_CLR (1 << 7)
95 #define CTAB(x) ((x) & (0x7 << 4))
96 #define CTAB_STOP_2 (0x0 << 4)
97 #define CTAB_STOP_8 (0x1 << 4)
98 #define CTAB_STOP_32 (0x2 << 4)
99 #define CTAB_STOP_128 (0x3 << 4)
100 #define CTAB_STOP_512 (0x4 << 4)
101 #define CTAB_STOP_2048 (0x5 << 4)
102 #define CTAB_STOP_8192 (0x6 << 4)
103 #define CTAB_STOP_32768 (0x7 << 4)
104 #define SWP_CNT_L (1 << 3)
105 #define CNT_EN (1 << 2)
106 #define CNT_SEL (1 << 1)
107 #define CNT_SET (1 << 0)
108
109/* W */
110#define IRQ_MSK 0x08
111 #define IM_MS (1 << 6)
112 #define IRQ_LVL (1 << 3)
113 #define IM_AFC (1 << 2)
114 #define IM_FS (1 << 1)
115 #define IM_CNT2 (1 << 0)
116
117/* W */
118#define FM_CAP 0x09
119
120/* R */
121#define CNT_L 0x0a /* Counter register low value */
122
123/* R */
124#define CNT_H 0x0b /* Counter register high value */
125
126/* R */
127#define CTRL_STAT 0x0c
128 #define AFC_FLG (1 << 0)
129
130/* R */
131#define RADIO_STAT 0x0d
132 #define RSS_MS (1 << 7)
133 #define RSS_FS(x) ((x) & 0x7f)
134 #define RSS_FS_GET(x) ((x) & 0x7f)
135 #define RSS_FS_SET(x) (x)
136/* Note: Reading this register will clear field strength and mono/stereo interrupt. */
137
138/* R */
139#define IRQ_ID 0x0e
140 #define II_CNT2 (1 << 5)
141 #define II_AFC (1 << 3)
142 #define II_FS_MS (1 << 0)
143
144/* W */
145#define IRQ_OUT 0x0f
146
147/* block 2 registers - offset added in order to id and avoid manual
148 switching */
149#define BLK2_START 0x10
150
151/* W */
152#define RADIO_CTRL1 (0x02 + BLK2_START)
153 #define EN_MEAS (1 << 7)
154 #define EN_AFC (1 << 6)
155 #define DIR_AFC (1 << 3)
156 #define RST_AFC (1 << 2)
157
158/* W */
159#define IF_CENTER (0x03 + BLK2_START)
160
161/* W */
162#define IF_BW (0x05 + BLK2_START)
163
164/* W */
165#define RADIO_CTRL2 (0x06 + BLK2_START)
166 #define VREF2 (1 << 7)
167 #define VREF (1 << 6)
168 #define STABI_BP (1 << 5)
169 #define IF_PM_L (1 << 4)
170 #define AGCSP (1 << 1)
171 #define AM_ANT_BSW (1 << 0) /* ?? */
172
173/* W */
174#define RADIO_CTRL3 (0x07 + BLK2_START)
175 #define AGC_SLVL (1 << 7)
176 #define VOLSH (1 << 6)
177 #define TB_ON (1 << 5)
178 #define AMUTE_L (1 << 4)
179 #define SE_FM (1 << 3)
180 #define SE_BE (1 << 1)
181 #define SE_EXT (1 << 0) /* For LV24000=0, LV24001/24002=Ext source enab. */
182
183/* W */
184#define STEREO_CTRL (0x08 + BLK2_START)
185 #define FRCST (1 << 7)
186 #define FMCS(x) ((x) & (0x7 << 4))
187 #define FMCS_GET(x) (((x) & (0x7 << 4)) >> 4)
188 #define FMCS_SET(x) ((x) << 4)
189 #define AUTOSSR (1 << 3)
190 #define PILTCA (1 << 2)
191 #define SD_PM (1 << 1)
192 #define ST_M (1 << 0)
193
194/* W */
195#define AUDIO_CTRL1 (0x09 + BLK2_START)
196 #define TONE_LVL(x) ((x) & (0xf << 4))
197 #define TONE_LVL_GET(x) (((x) & (0xf << 4)) >> 4)
198 #define TONE_LVL_SET(x) ((x) << 4)
199 #define VOL_LVL(x) ((x) & 0xf)
200 #define VOL_LVL_GET(x) ((x) & 0xf)
201 #define VOL_LVL_SET(x) ((x) << 4)
202
203/* W */
204#define AUDIO_CTRL2 (0x0a + BLK2_START)
205 #define BASS_PP (1 << 0)
206 #define BASS_P (1 << 1) /* BASS_P, BASS_N are mutually-exclusive */
207 #define BASS_N (1 << 2)
208 #define TREB_P (1 << 3) /* TREB_P, TREB_N are mutually-exclusive */
209 #define TREB_N (1 << 4)
210 #define DEEMP (1 << 5)
211 #define BPFREQ(x) ((x) & (0x3 << 6))
212 #define BPFREQ_2_0K (0x0 << 6)
213 #define BPFREQ_1_0K (0x1 << 6)
214 #define BPFREQ_0_5K (0x2 << 6)
215 #define BPFREQ_HIGH (0x3 << 6)
216
217/* W */
218#define PW_SCTRL (0x0b + BLK2_START)
219 #define SS_CTRL(x) ((x) & (0x7 << 5))
220 #define SS_CTRL_GET(x) (((x) & (0x7 << 5)) >> 5)
221 #define SS_CTRL_SET(x) ((x) << 5)
222 #define SM_CTRL(x) ((x) & (0x7 << 2))
223 #define SM_CTRL_GET(x) (((x) & (0x7 << 2)) >> 2)
224 #define SM_CTRL_SET(x) ((x) << 2)
225 #define PW_HPA (1 << 1) /* LV24002 only */
226 #define PW_RAD (1 << 0)
227
228/* shadow for writeable registers */
229#define TUNER_POWERED (1 << 0)
230#define TUNER_PRESENT (1 << 1)
231#define TUNER_AWAKE (1 << 2)
232#define TUNER_PRESENCE_CHECKED (1 << 3)
233static unsigned tuner_status = 0;
234
235static unsigned char lv24020lp_regs[0x1c];
236
237static const int sw_osc_low = 10; /* 30; */
238static const int sw_osc_high = 240; /* 200; */
239static const int sw_cap_low = 0;
240static const int sw_cap_high = 191;
241
242/* linear coefficients used for tuning */
243static int coef_00, coef_01, coef_10, coef_11;
244
245/* DAC control register set values */
246int if_set, sd_set;
247
248static inline bool tuner_awake(void)
249{
250 return (tuner_status & TUNER_AWAKE) != 0;
251}
252
253/* send a byte to the tuner - expects write mode to be current */
254static void lv24020lp_send_byte(unsigned int byte)
255{
256 int i;
257
258 byte <<= FM_DATA_PIN;
259
260 for (i = 0; i < 8; i++)
261 {
262 GPIOH_OUTPUT_VAL &= ~(1 << FM_CLOCK_PIN);
263
264 GPIOH_OUTPUT_VAL = (GPIOH_OUTPUT_VAL & ~(1 << FM_DATA_PIN)) |
265 (byte & (1 << FM_DATA_PIN));
266
267 GPIOH_OUTPUT_VAL |= (1 << FM_CLOCK_PIN);
268 udelay(FM_CLK_DELAY);
269
270 byte >>= 1;
271 }
272}
273
274/* end a write cycle on the tuner */
275static void lv24020lp_end_write(void)
276{
277 /* switch back to read mode */
278 GPIOH_OUTPUT_EN &= ~(1 << FM_DATA_PIN);
279 GPIOH_OUTPUT_VAL &= ~(1 << FM_NRW_PIN);
280}
281
282/* prepare a write cycle on the tuner */
283static unsigned int lv24020lp_begin_write(unsigned int address)
284{
285 /* Get register's block, translate address */
286 unsigned int blk = (address >= BLK2_START) ?
287 (address -= BLK2_START, BLK2) : BLK1;
288
289 for (;;)
290 {
291 /* Prepare 3-wire bus pins for write cycle */
292 GPIOH_OUTPUT_VAL |= (1 << FM_NRW_PIN);
293 GPIOH_OUTPUT_EN |= (1 << FM_DATA_PIN);
294
295 udelay(FM_CLK_DELAY);
296
297 /* current block == register block? */
298 if (blk == lv24020lp_regs[BLK_SEL])
299 return address;
300
301 /* switch block */
302 lv24020lp_regs[BLK_SEL] = blk;
303
304 /* data first */
305 lv24020lp_send_byte(blk);
306 /* then address */
307 lv24020lp_send_byte(BLK_SEL);
308
309 lv24020lp_end_write();
310
311 udelay(FM_CLK_DELAY);
312 }
313}
314
315/* write a byte to a tuner register */
316static void lv24020lp_write(unsigned int address, unsigned int data)
317{
318 /* shadow logical values but do logical=>physical remappings on some
319 registers' data. */
320 lv24020lp_regs[address] = data;
321
322 switch (address)
323 {
324 case FM_OSC:
325 /* L: 000..255
326 * P: 255..000 */
327 data = 255 - data;
328 break;
329 case FM_CAP:
330 /* L: 000..063, 064..191
331 * P: 255..192, 127..000 */
332 data = ((data < 64) ? 255 : (255 - 64)) - data;
333 break;
334 case RADIO_CTRL1:
335 /* L: data
336 * P: data | always "1" bits */
337 data |= (1 << 4) | (1 << 1) | (1 << 0);
338 break;
339 }
340
341 address = lv24020lp_begin_write(address);
342
343 /* data first */
344 lv24020lp_send_byte(data);
345 /* then address */
346 lv24020lp_send_byte(address);
347
348 lv24020lp_end_write();
349}
350
351/* helpers to set/clear register bits */
352static void lv24020lp_write_or(unsigned int address, unsigned int bits)
353{
354 lv24020lp_write(address, lv24020lp_regs[address] | bits);
355}
356
357static void lv24020lp_write_and(unsigned int address, unsigned int bits)
358{
359 lv24020lp_write(address, lv24020lp_regs[address] & bits);
360}
361
362/* read a byte from a tuner register */
363static unsigned int lv24020lp_read(unsigned int address)
364{
365 int i;
366 unsigned int toread;
367
368 address = lv24020lp_begin_write(address);
369
370 /* address */
371 lv24020lp_send_byte(address);
372
373 lv24020lp_end_write();
374
375 /* data */
376 toread = 0;
377 for (i = 0; i < 8; i++)
378 {
379 GPIOH_OUTPUT_VAL &= ~(1 << FM_CLOCK_PIN);
380 udelay(FM_CLK_DELAY);
381
382 toread |= (GPIOH_INPUT_VAL & (1 << FM_DATA_PIN)) << i;
383
384 GPIOH_OUTPUT_VAL |= (1 << FM_CLOCK_PIN);
385 }
386
387 return toread >> FM_DATA_PIN;
388}
389
390/* enables auto frequency centering */
391static void enable_afc(bool enabled)
392{
393 unsigned int radio_ctrl1 = lv24020lp_regs[RADIO_CTRL1];
394
395 if (enabled)
396 {
397 radio_ctrl1 &= ~RST_AFC;
398 radio_ctrl1 |= EN_AFC;
399 }
400 else
401 {
402 radio_ctrl1 |= RST_AFC;
403 radio_ctrl1 &= ~EN_AFC;
404 }
405
406 lv24020lp_write(RADIO_CTRL1, radio_ctrl1);
407}
408
409static int calculate_coef(unsigned fkhz)
410{
411 /* Overflow below 66000kHz --
412 My tuner tunes down to a min of ~72600kHz but datasheet mentions
413 66000kHz as the minimum. ?? Perhaps 76000kHz was intended? */
414 return fkhz < 66000 ?
415 0x7fffffff : 0x81d1a47efc5cb700ull / ((uint64_t)fkhz*fkhz);
416}
417
418static int interpolate_x(int expected_y, int x1, int x2, int y1, int y2)
419{
420 return y1 == y2 ?
421 0 : (int64_t)(expected_y - y1)*(x2 - x1) / (y2 - y1) + x1;
422}
423
424static int interpolate_y(int expected_x, int x1, int x2, int y1, int y2)
425{
426 return x1 == x2 ?
427 0 : (int64_t)(expected_x - x1)*(y2 - y1) / (x2 - x1) + y1;
428}
429
430/* this performs measurements of IF, FM and Stereo frequencies
431 * Input can be: MSS_FM, MSS_IF, MSS_SD */
432static int tuner_measure(unsigned char type, int scale, int duration)
433{
434 int64_t finval;
435
436 if (!tuner_awake())
437 return 0;
438
439 /* enable measuring */
440 lv24020lp_write_or(MSRC_SEL, type);
441 lv24020lp_write_and(CNT_CTRL, ~CNT_SEL);
442 lv24020lp_write_or(RADIO_CTRL1, EN_MEAS);
443
444 /* reset counter */
445 lv24020lp_write_or(CNT_CTRL, CNT1_CLR);
446 lv24020lp_write_and(CNT_CTRL, ~CNT1_CLR);
447
448 /* start counter, delay for specified time and stop it */
449 lv24020lp_write_or(CNT_CTRL, CNT_EN);
450 udelay(duration*1000 - 16);
451 lv24020lp_write_and(CNT_CTRL, ~CNT_EN);
452
453 /* read tick count */
454 finval = (lv24020lp_read(CNT_H) << 8) | lv24020lp_read(CNT_L);
455
456 /* restore measure mode */
457 lv24020lp_write_and(RADIO_CTRL1, ~EN_MEAS);
458 lv24020lp_write_and(MSRC_SEL, ~type);
459
460 /* convert value */
461 if (type == MSS_FM)
462 finval = scale*finval*256 / duration;
463 else
464 finval = scale*finval / duration;
465
466 return (int)finval;
467}
468
469/* set the FM oscillator frequency */
470static void set_frequency(int freq)
471{
472 int coef, cap_value, osc_value;
473 int f1, f2, x1, x2;
474 int count;
475
476 if (!tuner_awake())
477 return;
478
479 TUNER_LOG_OPEN();
480
481 TUNER_LOG("set_frequency(%d)\n", freq);
482
483 enable_afc(false);
484
485 /* MHz -> kHz */
486 freq /= 1000;
487
488 TUNER_LOG("Select cap:\n");
489
490 coef = calculate_coef(freq);
491 cap_value = interpolate_x(coef, sw_cap_low, sw_cap_high,
492 coef_00, coef_01);
493
494 osc_value = sw_osc_low;
495 lv24020lp_write(FM_OSC, osc_value);
496
497 /* Just in case - don't go into infinite loop */
498 for (count = 0; count < 30; count++)
499 {
500 int y0 = interpolate_y(cap_value, sw_cap_low, sw_cap_high,
501 coef_00, coef_01);
502 int y1 = interpolate_y(cap_value, sw_cap_low, sw_cap_high,
503 coef_10, coef_11);
504 int coef_fcur, cap_new, coef_cor, range;
505
506 lv24020lp_write(FM_CAP, cap_value);
507
508 range = y1 - y0;
509 f1 = tuner_measure(MSS_FM, 1, 16);
510 coef_fcur = calculate_coef(f1);
511 coef_cor = calculate_coef((f1*1000 + 32*256) / 1000);
512 y0 = coef_cor;
513 y1 = y0 + range;
514
515 TUNER_LOG("%d %d %d %d %d %d %d %d\n",
516 f1, cap_value, coef, coef_fcur, coef_cor, y0, y1, range);
517
518 if (coef >= y0 && coef <= y1)
519 {
520 osc_value = interpolate_x(coef, sw_osc_low, sw_osc_high,
521 y0, y1);
522
523 if (osc_value >= sw_osc_low && osc_value <= sw_osc_high)
524 break;
525 }
526
527 cap_new = interpolate_x(coef, cap_value, sw_cap_high,
528 coef_fcur, coef_01);
529
530 if (cap_new == cap_value)
531 {
532 if (coef < coef_fcur)
533 cap_value++;
534 else
535 cap_value--;
536 }
537 else
538 {
539 cap_value = cap_new;
540 }
541 }
542
543 TUNER_LOG("osc_value: %d\n", osc_value);
544
545 TUNER_LOG("Tune:\n");
546
547 x1 = sw_osc_low, x2 = sw_osc_high;
548 /* FM_OSC already at SW_OSC low and f1 is already the measured
549 frequency */
550
551 do
552 {
553 int x2_new;
554
555 lv24020lp_write(FM_OSC, x2);
556 f2 = tuner_measure(MSS_FM, 1, 16);
557
558 if (abs(f2 - freq) <= 16)
559 {
560 TUNER_LOG("%d %d %d %d\n", f1, f2, x1, x2);
561 break;
562 }
563
564 x2_new = interpolate_x(freq, x1, x2, f1, f2);
565
566 x1 = x2, f1 = f2, x2 = x2_new;
567 TUNER_LOG("%d %d %d %d\n", f1, f2, x1, x2);
568 }
569 while (x2 != 0);
570
571 if (x2 == 0)
572 {
573 /* May still be close enough */
574 TUNER_LOG("tuning failed - diff: %d\n", f2 - freq);
575 }
576
577 enable_afc(true);
578
579 TUNER_LOG("\n");
580
581 TUNER_LOG_SYNC();
582}
583
584static void fine_step_tune(int (*setcmp)(int regval), int regval, int step)
585{
586 /* Registers are not always stable, timeout if best fit not found soon
587 enough */
588 unsigned long abort = current_tick + HZ*2;
589 int flags = 0;
590
591 while (TIME_BEFORE(current_tick, abort))
592 {
593 int cmp;
594
595 regval = regval + step;
596
597 cmp = setcmp(regval);
598
599 if (cmp == 0)
600 break;
601
602 step = abs(step);
603
604 if (cmp < 0)
605 {
606 flags |= 1;
607 if (step == 1)
608 flags |= 4;
609 }
610 else
611 {
612 step = -step;
613 flags |= 2;
614 if (step == -1)
615 step |= 8;
616 }
617
618 if ((flags & 0xc) == 0xc)
619 break;
620
621 if ((flags & 0x3) == 0x3)
622 {
623 step /= 2;
624 if (step == 0)
625 step = 1;
626 flags &= ~3;
627 }
628 }
629}
630
631static int if_setcmp(int regval)
632{
633 lv24020lp_write(IF_OSC, regval);
634 lv24020lp_write(IF_CENTER, regval);
635 lv24020lp_write(IF_BW, 65*regval/100);
636
637 if_set = tuner_measure(MSS_IF, 1000, 32);
638
639 /* This register is bounces around by a few hundred Hz and doesn't seem
640 to be precisely tuneable. Just do 110000 +/- 500 since it's not very
641 critical it seems. */
642 if (abs(if_set - 109500) <= 500)
643 return 0;
644
645 return if_set < 109500 ? -1 : 1;
646}
647
648static int sd_setcmp(int regval)
649{
650 lv24020lp_write(SD_OSC, regval);
651
652 sd_set = tuner_measure(MSS_SD, 1000, 32);
653
654 if (abs(sd_set - 38300) <= 31)
655 return 0;
656
657 return sd_set < 38300 ? -1 : 1;
658}
659
660static void set_sleep(bool sleep)
661{
662 if (sleep || tuner_awake())
663 return;
664
665 if ((tuner_status & (TUNER_PRESENT | TUNER_POWERED)) !=
666 (TUNER_PRESENT | TUNER_POWERED))
667 return;
668
669 tuner_status |= TUNER_AWAKE;
670
671 enable_afc(false);
672
673 /* 2. Calibrate the IF frequency at 110 kHz: */
674 lv24020lp_write_and(RADIO_CTRL2, ~IF_PM_L);
675 fine_step_tune(if_setcmp, 0x80, 8);
676 lv24020lp_write_or(RADIO_CTRL2, IF_PM_L);
677
678 /* 3. Calibrate the stereo decoder clock at 38.3 kHz: */
679 lv24020lp_write_or(STEREO_CTRL, SD_PM);
680 fine_step_tune(sd_setcmp, 0x80, 8);
681 lv24020lp_write_and(STEREO_CTRL, ~SD_PM);
682
683 /* calculate FM tuning coefficients */
684 lv24020lp_write(FM_CAP, sw_cap_low);
685 lv24020lp_write(FM_OSC, sw_osc_low);
686 coef_00 = calculate_coef(tuner_measure(MSS_FM, 1, 64));
687
688 lv24020lp_write(FM_CAP, sw_cap_high);
689 coef_01 = calculate_coef(tuner_measure(MSS_FM, 1, 64));
690
691 lv24020lp_write(FM_CAP, sw_cap_low);
692 lv24020lp_write(FM_OSC, sw_osc_high);
693 coef_10 = calculate_coef(tuner_measure(MSS_FM, 1, 64));
694
695 lv24020lp_write(FM_CAP, sw_cap_high);
696 coef_11 = calculate_coef(tuner_measure(MSS_FM, 1, 64));
697
698 /* set various audio level settings */
699 lv24020lp_write(AUDIO_CTRL1, TONE_LVL_SET(0) | VOL_LVL_SET(0));
700 lv24020lp_write_or(RADIO_CTRL2, AGCSP);
701 lv24020lp_write_or(RADIO_CTRL3, VOLSH);
702 lv24020lp_write(STEREO_CTRL, FMCS_SET(7) | AUTOSSR);
703 lv24020lp_write(PW_SCTRL, SS_CTRL_SET(3) | SM_CTRL_SET(1) |
704 PW_RAD);
705}
706
707/** Public interfaces **/
708void lv24020lp_power(bool status)
709{
710 static const unsigned char tuner_defaults[][2] =
711 {
712 /* Block 1 writeable registers */
713 { MSRC_SEL, AFC_LVL },
714 { FM_OSC, 0x80 },
715 { SD_OSC, 0x80 },
716 { IF_OSC, 0x80 },
717 { CNT_CTRL, CNT1_CLR | SWP_CNT_L },
718 { IRQ_MSK, 0x00 }, /* IRQ_LVL -> Low to High */
719 { FM_CAP, 0x80 },
720 /* { IRQ_OUT, 0x00 }, No action on this register (skip) */
721 /* Block 2 writeable registers */
722 { RADIO_CTRL1, EN_AFC },
723 { IF_CENTER, 0x80 },
724 { IF_BW, 65*0x80 / 100 }, /* 65% of IF_OSC */
725 { RADIO_CTRL2, IF_PM_L },
726 { RADIO_CTRL3, AGC_SLVL | SE_FM },
727 { STEREO_CTRL, FMCS_SET(4) | AUTOSSR },
728 { AUDIO_CTRL1, TONE_LVL_SET(7) | VOL_LVL_SET(7) },
729 { AUDIO_CTRL2, BPFREQ_HIGH }, /* deemphasis 50us */
730 { PW_SCTRL, SS_CTRL_SET(3) | SM_CTRL_SET(3) | PW_RAD },
731 };
732
733 unsigned i;
734
735 if (status)
736 {
737 tuner_status |= TUNER_POWERED | TUNER_PRESENCE_CHECKED;
738
739 /* if tuner is present, CHIP ID is 0x09 */
740 if (lv24020lp_read(CHIP_ID) == 0x09)
741 {
742 tuner_status |= TUNER_PRESENT;
743
744 /* After power-up, the LV2400x needs to be initialized as
745 follows: */
746
747 /* 1. Write default values to the registers: */
748 lv24020lp_regs[BLK_SEL] = 0; /* Force a switch on the first */
749 for (i = 0; i < ARRAYLEN(tuner_defaults); i++)
750 lv24020lp_write(tuner_defaults[i][0], tuner_defaults[i][1]);
751
752 /* Complete the startup calibration if the tuner is woken */
753 udelay(100000);
754 }
755 }
756 else
757 {
758 /* Power off */
759 if (tuner_status & TUNER_PRESENT)
760 lv24020lp_write_and(PW_SCTRL, ~PW_RAD);
761
762 tuner_status &= ~(TUNER_POWERED | TUNER_AWAKE);
763 }
764}
765
766int lv24020lp_set(int setting, int value)
767{
768 int val = 1;
769
770 switch(setting)
771 {
772 case RADIO_SLEEP:
773 set_sleep(value);
774 break;
775
776 case RADIO_FREQUENCY:
777 set_frequency(value);
778 break;
779
780 case RADIO_SCAN_FREQUENCY:
781 /* TODO: really implement this */
782 set_frequency(value);
783 val = lv24020lp_get(RADIO_TUNED);
784 break;
785
786 case RADIO_MUTE:
787 if (value)
788 lv24020lp_write_and(RADIO_CTRL3, ~AMUTE_L);
789 else
790 lv24020lp_write_or(RADIO_CTRL3, AMUTE_L);
791 break;
792
793 case RADIO_REGION:
794 {
795 if (lv24020lp_region_data[value])
796 lv24020lp_write_or(AUDIO_CTRL2, DEEMP);
797 else
798 lv24020lp_write_and(AUDIO_CTRL2, ~DEEMP);
799 break;
800 }
801
802 case RADIO_FORCE_MONO:
803 if (value)
804 lv24020lp_write_or(STEREO_CTRL, ST_M);
805 else
806 lv24020lp_write_and(STEREO_CTRL, ~ST_M);
807 break;
808
809 default:
810 val = -1;
811 }
812
813 return val;
814}
815
816int lv24020lp_get(int setting)
817{
818 int val = -1;
819
820 switch(setting)
821 {
822 case RADIO_TUNED:
823 /* TODO: really implement this */
824 val = RSS_FS(lv24020lp_read(RADIO_STAT)) < 0x1f;
825 break;
826
827 case RADIO_STEREO:
828 val = (lv24020lp_read(RADIO_STAT) & RSS_MS) != 0;
829 break;
830
831 case RADIO_PRESENT:
832 {
833 bool fmstatus = true;
834
835 if (!(tuner_status & TUNER_PRESENCE_CHECKED))
836 fmstatus = tuner_power(true);
837
838 val = (tuner_status & TUNER_PRESENT) != 0;
839
840 if (!fmstatus)
841 tuner_power(false);
842 break;
843 }
844
845 /* tuner-specific debug info */
846 case LV24020LP_CTRL_STAT:
847 return lv24020lp_read(CTRL_STAT);
848
849 case LV24020LP_REG_STAT:
850 return lv24020lp_read(RADIO_STAT);
851
852 case LV24020LP_MSS_FM:
853 return tuner_measure(MSS_FM, 1, 16);
854
855 case LV24020LP_MSS_IF:
856 return tuner_measure(MSS_IF, 1000, 16);
857
858 case LV24020LP_MSS_SD:
859 return tuner_measure(MSS_SD, 1000, 16);
860
861 case LV24020LP_IF_SET:
862 return if_set;
863
864 case LV24020LP_SD_SET:
865 return sd_set;
866 }
867
868 return val;
869}
870#endif /* BOOTLOADER */
diff --git a/firmware/drivers/tuner/s1a0903x01.c b/firmware/drivers/tuner/s1a0903x01.c
new file mode 100644
index 0000000000..cdeba2b3b4
--- /dev/null
+++ b/firmware/drivers/tuner/s1a0903x01.c
@@ -0,0 +1,172 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 * Tuner "middleware" for Samsung S1A0903X01 chip
10 *
11 * Copyright (C) 2003 Linus Nielsen Feltzing
12 *
13 * All files in this archive are subject to the GNU General Public License.
14 * See the file COPYING in the source tree root for full license agreement.
15 *
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
18 *
19 ****************************************************************************/
20
21#include <stdbool.h>
22#include <stdlib.h>
23#include "config.h"
24#include "kernel.h"
25#include "tuner.h" /* tuner abstraction interface */
26#include "fmradio.h" /* physical interface driver */
27#include "mpeg.h"
28#include "sound.h"
29
30#define DEFAULT_IN1 0x100003 /* Mute */
31#define DEFAULT_IN2 0x140884 /* 5kHz, 7.2MHz crystal */
32#define PLL_FREQ_STEP 10000
33
34static int fm_in1;
35static int fm_in2;
36static int fm_present = -1; /* unknown */
37
38/* tuner abstraction layer: set something to the tuner */
39int s1a0903x01_set(int setting, int value)
40{
41 int val = 1;
42
43 switch(setting)
44 {
45 case RADIO_SLEEP:
46 if (!value)
47 { /* wakeup: just unit */
48 fm_in1 = DEFAULT_IN1;
49 fm_in2 = DEFAULT_IN2;
50 fmradio_set(1, fm_in1);
51 fmradio_set(2, fm_in2);
52 }
53 /* else we have no sleep mode? */
54 break;
55
56 case RADIO_FREQUENCY:
57 {
58 int pll_cnt;
59#if CONFIG_CODEC == MAS3587F
60 /* Shift the MAS internal clock away for certain frequencies to
61 * avoid interference. */
62 int pitch = 1000;
63
64 /* 4th harmonic falls in the FM frequency range */
65 int if_freq = 4 * mpeg_get_mas_pllfreq();
66
67 /* shift the mas harmonic >= 300 kHz away using the direction
68 * which needs less shifting. */
69 if (value < if_freq)
70 {
71 if (if_freq - value < 300000)
72 pitch = 1003 - (if_freq - value) / 100000;
73 }
74 else
75 {
76 if (value - if_freq < 300000)
77 pitch = 997 + (value - if_freq) / 100000;
78 }
79 sound_set_pitch(pitch);
80#endif
81 /* We add the standard Intermediate Frequency 10.7MHz
82 ** before calculating the divisor
83 ** The reference frequency is set to 50kHz, and the VCO
84 ** output is prescaled by 2.
85 */
86
87 pll_cnt = (value + 10700000) / (PLL_FREQ_STEP/2) / 2;
88
89 /* 0x100000 == FM mode
90 ** 0x000002 == Microprocessor controlled Mute
91 */
92 fm_in1 = (fm_in1 & 0xfff00007) | (pll_cnt << 3);
93 fmradio_set(1, fm_in1);
94 break;
95 }
96
97 case RADIO_SCAN_FREQUENCY:
98 /* Tune in and delay */
99 s1a0903x01_set(RADIO_FREQUENCY, value);
100 sleep(1);
101 /* Start IF measurement */
102 fm_in1 |= 4;
103 fmradio_set(1, fm_in1);
104 sleep(1);
105 val = s1a0903x01_get(RADIO_TUNED);
106 break;
107
108 case RADIO_MUTE:
109 fm_in1 = (fm_in1 & 0xfffffffe) | (value?1:0);
110 fmradio_set(1, fm_in1);
111 break;
112
113 case RADIO_FORCE_MONO:
114 fm_in2 = (fm_in2 & 0xfffffffb) | (value?0:4);
115 fmradio_set(2, fm_in2);
116 break;
117 /* NOTE: These were only zeroed when starting the tuner from OFF
118 but the default values already set them to 0. */
119#if 0
120 case S1A0903X01_IF_MEASUREMENT:
121 fm_in1 = (fm_in1 & 0xfffffffb) | (value?4:0);
122 fmradio_set(1, fm_in1);
123 break;
124
125 case S1A0903X01_SENSITIVITY:
126 fm_in2 = (fm_in2 & 0xffff9fff) | ((value & 3) << 13);
127 fmradio_set(2, fm_in2);
128 break;
129#endif
130 default:
131 val = -1;
132 }
133
134 return val;
135}
136
137/* tuner abstraction layer: read something from the tuner */
138int s1a0903x01_get(int setting)
139{
140 int val = -1;
141 switch(setting)
142 {
143 case RADIO_PRESENT:
144 if (fm_present == -1)
145 {
146#ifdef HAVE_TUNER_PWR_CTRL
147 bool fmstatus = tuner_power(true);
148#endif
149 /* 5kHz, 7.2MHz crystal, test mode 1 */
150 fmradio_set(2, 0x140885);
151 fm_present = (fmradio_read(0) == 0x140885);
152#ifdef HAVE_TUNER_PWR_CTRL
153 if (!fmstatus)
154 tuner_power(false);
155#endif
156 }
157
158 val = fm_present;
159 break;
160
161 case RADIO_TUNED:
162 val = fmradio_read(3);
163 val = abs(10700 - ((val & 0x7ffff) / 8)) < 50; /* convert to kHz */
164 break;
165
166 case RADIO_STEREO:
167 val = fmradio_read(3);
168 val = ((val & 0x100000) ? true : false);
169 break;
170 }
171 return val;
172}
diff --git a/firmware/drivers/tuner/tea5767.c b/firmware/drivers/tuner/tea5767.c
new file mode 100644
index 0000000000..da7cdfb65a
--- /dev/null
+++ b/firmware/drivers/tuner/tea5767.c
@@ -0,0 +1,135 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 * Tuner "middleware" for Philips TEA5767 chip
10 *
11 * Copyright (C) 2004 Jörg Hohensohn
12 *
13 * All files in this archive are subject to the GNU General Public License.
14 * See the file COPYING in the source tree root for full license agreement.
15 *
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
18 *
19 ****************************************************************************/
20#include "config.h"
21#include <stdbool.h>
22#include <string.h>
23#include <stdlib.h>
24#include "kernel.h"
25#include "tuner.h" /* tuner abstraction interface */
26#include "fmradio.h"
27#include "fmradio_i2c.h" /* physical interface driver */
28
29#define I2C_ADR 0xC0
30static unsigned char write_bytes[5] = { 0x00, 0x00, 0x00, 0x00, 0x00 };
31
32static void tea5767_set_clear(int byte, unsigned char bits, int set)
33{
34 write_bytes[byte] &= ~bits;
35 if (set)
36 write_bytes[byte] |= bits;
37}
38
39/* tuner abstraction layer: set something to the tuner */
40int tea5767_set(int setting, int value)
41{
42 switch(setting)
43 {
44 case RADIO_SLEEP:
45 /* init values */
46 write_bytes[0] |= (1<<7); /* mute */
47#if CONFIG_TUNER_XTAL == 32768
48 /* 32.768kHz, soft mute, stereo noise cancelling */
49 write_bytes[3] |= (1<<4) | (1<<3) | (1<<1);
50#else
51 /* soft mute, stereo noise cancelling */
52 write_bytes[3] |= (1<<3) | (1<<1);
53#endif
54 /* sleep / standby mode */
55 tea5767_set_clear(3, (1<<6), value);
56 break;
57
58 case RADIO_FREQUENCY:
59 {
60 int n;
61#if CONFIG_TUNER_XTAL == 32768
62 n = (4 * (value - 225000) + 16384) / 32768;
63#else
64 n = (4 * (value - 225000)) / 50000;
65#endif
66 write_bytes[0] = (write_bytes[0] & 0xC0) | (n >> 8);
67 write_bytes[1] = n;
68 }
69 break;
70
71 case RADIO_SCAN_FREQUENCY:
72 tea5767_set(RADIO_FREQUENCY, value);
73 sleep(HZ/30);
74 return tea5767_get(RADIO_TUNED);
75
76 case RADIO_MUTE:
77 tea5767_set_clear(0, 0x80, value);
78 break;
79
80 case RADIO_REGION:
81 {
82 const struct tea5767_region_data *rd =
83 &tea5767_region_data[value];
84
85 tea5767_set_clear(4, (1<<6), rd->deemphasis);
86 tea5767_set_clear(3, (1<<5), rd->band);
87 break;
88 }
89 case RADIO_FORCE_MONO:
90 tea5767_set_clear(2, 0x08, value);
91 break;
92 default:
93 return -1;
94 }
95
96 fmradio_i2c_write(I2C_ADR, write_bytes, sizeof(write_bytes));
97 return 1;
98}
99
100/* tuner abstraction layer: read something from the tuner */
101int tea5767_get(int setting)
102{
103 unsigned char read_bytes[5];
104 int val = -1; /* default for unsupported query */
105
106 fmradio_i2c_read(I2C_ADR, read_bytes, sizeof(read_bytes));
107
108 switch(setting)
109 {
110 case RADIO_PRESENT:
111 val = 1; /* true */
112 break;
113
114 case RADIO_TUNED:
115 val = 0;
116 if (read_bytes[0] & 0x80) /* ready */
117 {
118 val = read_bytes[2] & 0x7F; /* IF counter */
119 val = (abs(val - 0x36) < 2); /* close match */
120 }
121 break;
122
123 case RADIO_STEREO:
124 val = read_bytes[2] >> 7;
125 break;
126 }
127
128 return val;
129}
130
131void tea5767_dbg_info(struct tea5767_dbg_info *info)
132{
133 fmradio_i2c_read(I2C_ADR, info->read_regs, 5);
134 memcpy(info->write_regs, write_bytes, 5);
135}