diff options
author | Michael Sevakis <jethead71@rockbox.org> | 2007-07-14 11:20:31 +0000 |
---|---|---|
committer | Michael Sevakis <jethead71@rockbox.org> | 2007-07-14 11:20:31 +0000 |
commit | 7d759f6b9ca96a4a64c71ac301eb59cb9702e74c (patch) | |
tree | fbaf5104716ccfed888c7d8506dbee15a881d4a2 /firmware/drivers | |
parent | b51a20fb9b1cc57b813d1c0b90ad8c010b9eab84 (diff) | |
download | rockbox-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.c | 4 | ||||
-rw-r--r-- | firmware/drivers/tuner/lv24020lp.c | 870 | ||||
-rw-r--r-- | firmware/drivers/tuner/s1a0903x01.c | 172 | ||||
-rw-r--r-- | firmware/drivers/tuner/tea5767.c | 135 |
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 | ||
38 | static bool powered = false; | 38 | static bool powered = false; |
39 | 39 | ||
40 | bool radio_powered(void) | 40 | bool tuner_powered(void) |
41 | { | 41 | { |
42 | return powered; | 42 | return powered; |
43 | } | 43 | } |
44 | 44 | ||
45 | bool radio_power(bool status) | 45 | bool 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 | |||
43 | static 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) | ||
233 | static unsigned tuner_status = 0; | ||
234 | |||
235 | static unsigned char lv24020lp_regs[0x1c]; | ||
236 | |||
237 | static const int sw_osc_low = 10; /* 30; */ | ||
238 | static const int sw_osc_high = 240; /* 200; */ | ||
239 | static const int sw_cap_low = 0; | ||
240 | static const int sw_cap_high = 191; | ||
241 | |||
242 | /* linear coefficients used for tuning */ | ||
243 | static int coef_00, coef_01, coef_10, coef_11; | ||
244 | |||
245 | /* DAC control register set values */ | ||
246 | int if_set, sd_set; | ||
247 | |||
248 | static 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 */ | ||
254 | static 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 */ | ||
275 | static 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 */ | ||
283 | static 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 */ | ||
316 | static 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 */ | ||
352 | static void lv24020lp_write_or(unsigned int address, unsigned int bits) | ||
353 | { | ||
354 | lv24020lp_write(address, lv24020lp_regs[address] | bits); | ||
355 | } | ||
356 | |||
357 | static 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 */ | ||
363 | static 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 */ | ||
391 | static 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 | |||
409 | static 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 | |||
418 | static 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 | |||
424 | static 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 */ | ||
432 | static 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 */ | ||
470 | static 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 | |||
584 | static 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 | |||
631 | static 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 | |||
648 | static 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 | |||
660 | static 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 **/ | ||
708 | void 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 | |||
766 | int 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 | |||
816 | int 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 | |||
34 | static int fm_in1; | ||
35 | static int fm_in2; | ||
36 | static int fm_present = -1; /* unknown */ | ||
37 | |||
38 | /* tuner abstraction layer: set something to the tuner */ | ||
39 | int 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 */ | ||
138 | int 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 | ||
30 | static unsigned char write_bytes[5] = { 0x00, 0x00, 0x00, 0x00, 0x00 }; | ||
31 | |||
32 | static 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 */ | ||
40 | int 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 */ | ||
101 | int 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 | |||
131 | void 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 | } | ||