| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * Tuner driver for the Sanyo LV24020LP |
| * |
| * Copyright (C) 2007 Ivan Zupan |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ****************************************************************************/ |
| |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include "config.h" |
| #include "thread.h" |
| #include "kernel.h" |
| #include "tuner.h" /* tuner abstraction interface */ |
| #include "power.h" |
| #include "fmradio.h" /* physical interface driver */ |
| #include "sound.h" |
| #include "pp5024.h" |
| #include "system.h" |
| |
| #ifndef BOOTLOADER |
| |
| static struct mutex tuner_mtx; |
| |
| #if 0 |
| /* define to enable tuner logging */ |
| #define SANYO_TUNER_LOG |
| #endif |
| |
| #ifdef SANYO_TUNER_LOG |
| #include "sprintf.h" |
| #include "file.h" |
| |
| static int fd_log = -1; |
| |
| #define TUNER_LOG_OPEN() if (fd_log < 0) \ |
| fd_log = creat("/tuner_dump.txt") |
| /* syncing required because close() is never called */ |
| #define TUNER_LOG_SYNC() fsync(fd_log) |
| #define TUNER_LOG(s...) fdprintf(fd_log, s) |
| #else |
| #define TUNER_LOG_OPEN() |
| #define TUNER_LOG_SYNC() |
| #define TUNER_LOG(s...) |
| #endif /* SANYO_TUNER_LOG */ |
| |
| /** tuner register defines **/ |
| |
| #if defined(SANSA_E200) || defined(SANSA_C200) |
| #define GPIO_OUTPUT_EN GPIOH_OUTPUT_EN |
| #define GPIO_OUTPUT_VAL GPIOH_OUTPUT_VAL |
| #define GPIO_INPUT_VAL GPIOH_INPUT_VAL |
| #define FM_NRW_PIN 3 |
| #define FM_CLOCK_PIN 4 |
| #define FM_DATA_PIN 5 |
| #elif defined(COWON_D2) |
| #define GPIO_OUTPUT_EN GPIOC_DIR |
| #define GPIO_OUTPUT_VAL GPIOC |
| #define GPIO_INPUT_VAL GPIOC |
| #define FM_NRW_PIN 31 |
| #define FM_CLOCK_PIN 29 |
| #define FM_DATA_PIN 30 |
| #define udelay(x) /* Remove hack when D2 has udelay */ |
| #else |
| #error GPIOs undefined for this target |
| #endif |
| |
| #define FM_CLK_DELAY 1 |
| |
| /* block 1 registers */ |
| |
| /* R */ |
| #define CHIP_ID 0x00 |
| |
| /* W */ |
| #define BLK_SEL 0x01 |
| #define BLK1 0x01 |
| #define BLK2 0x02 |
| |
| /* W */ |
| #define MSRC_SEL 0x02 |
| #define MSR_O (1 << 7) |
| #define AFC_LVL (1 << 6) |
| #define AFC_SPD (1 << 5) |
| #define MSS_SD (1 << 2) |
| #define MSS_FM (1 << 1) |
| #define MSS_IF (1 << 0) |
| |
| /* W */ |
| #define FM_OSC 0x03 |
| |
| /* W */ |
| #define SD_OSC 0x04 |
| |
| /* W */ |
| #define IF_OSC 0x05 |
| |
| /* W */ |
| #define CNT_CTRL 0x06 |
| #define CNT1_CLR (1 << 7) |
| #define CTAB(x) ((x) & (0x7 << 4)) |
| #define CTAB_STOP_2 (0x0 << 4) |
| #define CTAB_STOP_8 (0x1 << 4) |
| #define CTAB_STOP_32 (0x2 << 4) |
| #define CTAB_STOP_128 (0x3 << 4) |
| #define CTAB_STOP_512 (0x4 << 4) |
| #define CTAB_STOP_2048 (0x5 << 4) |
| #define CTAB_STOP_8192 (0x6 << 4) |
| #define CTAB_STOP_32768 (0x7 << 4) |
| #define SWP_CNT_L (1 << 3) |
| #define CNT_EN (1 << 2) |
| #define CNT_SEL (1 << 1) |
| #define CNT_SET (1 << 0) |
| |
| /* W */ |
| #define IRQ_MSK 0x08 |
| #define IM_MS (1 << 6) |
| #define IRQ_LVL (1 << 3) |
| #define IM_AFC (1 << 2) |
| #define IM_FS (1 << 1) |
| #define IM_CNT2 (1 << 0) |
| |
| /* W */ |
| #define FM_CAP 0x09 |
| |
| /* R */ |
| #define CNT_L 0x0a /* Counter register low value */ |
| |
| /* R */ |
| #define CNT_H 0x0b /* Counter register high value */ |
| |
| /* R */ |
| #define CTRL_STAT 0x0c |
| #define AFC_FLG (1 << 0) |
| |
| /* R */ |
| #define RADIO_STAT 0x0d |
| #define RSS_MS (1 << 7) |
| #define RSS_FS(x) ((x) & 0x7f) |
| #define RSS_FS_GET(x) ((x) & 0x7f) |
| #define RSS_FS_SET(x) (x) |
| /* Note: Reading this register will clear field strength and mono/stereo interrupt. */ |
| |
| /* R */ |
| #define IRQ_ID 0x0e |
| #define II_CNT2 (1 << 5) |
| #define II_AFC (1 << 3) |
| #define II_FS_MS (1 << 0) |
| |
| /* W */ |
| #define IRQ_OUT 0x0f |
| |
| /* block 2 registers - offset added in order to id and avoid manual |
| switching */ |
| #define BLK2_START 0x10 |
| |
| /* W */ |
| #define RADIO_CTRL1 (0x02 + BLK2_START) |
| #define EN_MEAS (1 << 7) |
| #define EN_AFC (1 << 6) |
| #define DIR_AFC (1 << 3) |
| #define RST_AFC (1 << 2) |
| |
| /* W */ |
| #define IF_CENTER (0x03 + BLK2_START) |
| |
| /* W */ |
| #define IF_BW (0x05 + BLK2_START) |
| |
| /* W */ |
| #define RADIO_CTRL2 (0x06 + BLK2_START) |
| #define VREF2 (1 << 7) |
| #define VREF (1 << 6) |
| #define STABI_BP (1 << 5) |
| #define IF_PM_L (1 << 4) |
| #define AGCSP (1 << 1) |
| #define AM_ANT_BSW (1 << 0) /* ?? */ |
| |
| /* W */ |
| #define RADIO_CTRL3 (0x07 + BLK2_START) |
| #define AGC_SLVL (1 << 7) |
| #define VOLSH (1 << 6) |
| #define TB_ON (1 << 5) |
| #define AMUTE_L (1 << 4) |
| #define SE_FM (1 << 3) |
| #define SE_BE (1 << 1) |
| #define SE_EXT (1 << 0) /* For LV24000=0, LV24001/24002=Ext source enab. */ |
| |
| /* W */ |
| #define STEREO_CTRL (0x08 + BLK2_START) |
| #define FRCST (1 << 7) |
| #define FMCS(x) ((x) & (0x7 << 4)) |
| #define FMCS_GET(x) (((x) & (0x7 << 4)) >> 4) |
| #define FMCS_SET(x) ((x) << 4) |
| #define AUTOSSR (1 << 3) |
| #define PILTCA (1 << 2) |
| #define SD_PM (1 << 1) |
| #define ST_M (1 << 0) |
| |
| /* W */ |
| #define AUDIO_CTRL1 (0x09 + BLK2_START) |
| #define TONE_LVL(x) ((x) & (0xf << 4)) |
| #define TONE_LVL_GET(x) (((x) & (0xf << 4)) >> 4) |
| #define TONE_LVL_SET(x) ((x) << 4) |
| #define VOL_LVL(x) ((x) & 0xf) |
| #define VOL_LVL_GET(x) ((x) & 0xf) |
| #define VOL_LVL_SET(x) ((x) << 4) |
| |
| /* W */ |
| #define AUDIO_CTRL2 (0x0a + BLK2_START) |
| #define BASS_PP (1 << 0) |
| #define BASS_P (1 << 1) /* BASS_P, BASS_N are mutually-exclusive */ |
| #define BASS_N (1 << 2) |
| #define TREB_P (1 << 3) /* TREB_P, TREB_N are mutually-exclusive */ |
| #define TREB_N (1 << 4) |
| #define DEEMP (1 << 5) |
| #define BPFREQ(x) ((x) & (0x3 << 6)) |
| #define BPFREQ_2_0K (0x0 << 6) |
| #define BPFREQ_1_0K (0x1 << 6) |
| #define BPFREQ_0_5K (0x2 << 6) |
| #define BPFREQ_HIGH (0x3 << 6) |
| |
| /* W */ |
| #define PW_SCTRL (0x0b + BLK2_START) |
| #define SS_CTRL(x) ((x) & (0x7 << 5)) |
| #define SS_CTRL_GET(x) (((x) & (0x7 << 5)) >> 5) |
| #define SS_CTRL_SET(x) ((x) << 5) |
| #define SM_CTRL(x) ((x) & (0x7 << 2)) |
| #define SM_CTRL_GET(x) (((x) & (0x7 << 2)) >> 2) |
| #define SM_CTRL_SET(x) ((x) << 2) |
| #define PW_HPA (1 << 1) /* LV24002 only */ |
| #define PW_RAD (1 << 0) |
| |
| /* shadow for writeable registers */ |
| #define TUNER_POWERED (1 << 0) |
| #define TUNER_PRESENT (1 << 1) |
| #define TUNER_AWAKE (1 << 2) |
| #define TUNER_PRESENCE_CHECKED (1 << 3) |
| static unsigned tuner_status = 0; |
| |
| static unsigned char lv24020lp_regs[0x1c]; |
| |
| static const int sw_osc_low = 10; /* 30; */ |
| static const int sw_osc_high = 240; /* 200; */ |
| static const int sw_cap_low = 0; |
| static const int sw_cap_high = 191; |
| |
| /* linear coefficients used for tuning */ |
| static int coef_00, coef_01, coef_10, coef_11; |
| |
| /* DAC control register set values */ |
| static int if_set, sd_set; |
| |
| static inline bool tuner_awake(void) |
| { |
| return (tuner_status & TUNER_AWAKE) != 0; |
| } |
| |
| /* send a byte to the tuner - expects write mode to be current */ |
| static void lv24020lp_send_byte(unsigned int byte) |
| { |
| int i; |
| |
| for (i = 0; i < 8; i++) |
| { |
| GPIO_OUTPUT_VAL &= ~(1 << FM_CLOCK_PIN); |
| |
| if (byte & 1) |
| GPIO_OUTPUT_VAL |= (1 << FM_DATA_PIN); |
| else |
| GPIO_OUTPUT_VAL &= ~(1 << FM_DATA_PIN); |
| |
| udelay(FM_CLK_DELAY); |
| |
| GPIO_OUTPUT_VAL |= (1 << FM_CLOCK_PIN); |
| udelay(FM_CLK_DELAY); |
| |
| byte >>= 1; |
| } |
| } |
| |
| /* end a write cycle on the tuner */ |
| static void lv24020lp_end_write(void) |
| { |
| /* switch back to read mode */ |
| GPIO_OUTPUT_EN &= ~(1 << FM_DATA_PIN); |
| GPIO_OUTPUT_VAL &= ~(1 << FM_NRW_PIN); |
| udelay(FM_CLK_DELAY); |
| } |
| |
| /* prepare a write cycle on the tuner */ |
| static unsigned int lv24020lp_begin_write(unsigned int address) |
| { |
| /* Get register's block, translate address */ |
| unsigned int blk = (address >= BLK2_START) ? |
| (address -= BLK2_START, BLK2) : BLK1; |
| |
| for (;;) |
| { |
| /* Prepare 3-wire bus pins for write cycle */ |
| GPIO_OUTPUT_VAL |= (1 << FM_NRW_PIN); |
| GPIO_OUTPUT_EN |= (1 << FM_DATA_PIN); |
| udelay(FM_CLK_DELAY); |
| |
| /* current block == register block? */ |
| if (blk == lv24020lp_regs[BLK_SEL]) |
| return address; |
| |
| /* switch block */ |
| lv24020lp_regs[BLK_SEL] = blk; |
| |
| /* data first */ |
| lv24020lp_send_byte(blk); |
| /* then address */ |
| lv24020lp_send_byte(BLK_SEL); |
| |
| lv24020lp_end_write(); |
| } |
| } |
| |
| /* write a byte to a tuner register */ |
| static void lv24020lp_write(unsigned int address, unsigned int data) |
| { |
| /* shadow logical values but do logical=>physical remappings on some |
| registers' data. */ |
| lv24020lp_regs[address] = data; |
| |
| switch (address) |
| { |
| case FM_OSC: |
| /* L: 000..255 |
| * P: 255..000 */ |
| data = 255 - data; |
| break; |
| case FM_CAP: |
| /* L: 000..063, 064..191 |
| * P: 255..192, 127..000 */ |
| data = ((data < 64) ? 255 : (255 - 64)) - data; |
| break; |
| case RADIO_CTRL1: |
| /* L: data |
| * P: data | always "1" bits */ |
| data |= (1 << 4) | (1 << 1) | (1 << 0); |
| break; |
| } |
| |
| /* Check if interface is turned on */ |
| if (!(tuner_status & TUNER_POWERED)) |
| return; |
| |
| address = lv24020lp_begin_write(address); |
| |
| /* data first */ |
| lv24020lp_send_byte(data); |
| /* then address */ |
| lv24020lp_send_byte(address); |
| |
| lv24020lp_end_write(); |
| } |
| |
| /* helpers to set/clear register bits */ |
| static void lv24020lp_write_set(unsigned int address, unsigned int bits) |
| { |
| lv24020lp_write(address, lv24020lp_regs[address] | bits); |
| } |
| |
| static void lv24020lp_write_clear(unsigned int address, unsigned int bits) |
| { |
| lv24020lp_write(address, lv24020lp_regs[address] & ~bits); |
| } |
| |
| /* read a byte from a tuner register */ |
| static unsigned int lv24020lp_read(unsigned int address) |
| { |
| int i; |
| unsigned int toread; |
| |
| /* Check if interface is turned on */ |
| if (!(tuner_status & TUNER_POWERED)) |
| return 0; |
| |
| address = lv24020lp_begin_write(address); |
| |
| /* address */ |
| lv24020lp_send_byte(address); |
| |
| lv24020lp_end_write(); |
| |
| /* data */ |
| toread = 0; |
| for (i = 0; i < 8; i++) |
| { |
| GPIO_OUTPUT_VAL &= ~(1 << FM_CLOCK_PIN); |
| udelay(FM_CLK_DELAY); |
| |
| if (GPIO_INPUT_VAL & (1 << FM_DATA_PIN)) |
| toread |= (1 << i); |
| |
| GPIO_OUTPUT_VAL |= (1 << FM_CLOCK_PIN); |
| udelay(FM_CLK_DELAY); |
| } |
| |
| return toread; |
| } |
| |
| /* enables auto frequency centering */ |
| static void enable_afc(bool enabled) |
| { |
| unsigned int radio_ctrl1 = lv24020lp_regs[RADIO_CTRL1]; |
| |
| if (enabled) |
| { |
| radio_ctrl1 &= ~RST_AFC; |
| radio_ctrl1 |= EN_AFC; |
| } |
| else |
| { |
| radio_ctrl1 |= RST_AFC; |
| radio_ctrl1 &= ~EN_AFC; |
| } |
| |
| lv24020lp_write(RADIO_CTRL1, radio_ctrl1); |
| } |
| |
| static int calculate_coef(unsigned fkhz) |
| { |
| /* Overflow below 66000kHz -- |
| My tuner tunes down to a min of ~72600kHz but datasheet mentions |
| 66000kHz as the minimum. ?? Perhaps 76000kHz was intended? */ |
| return fkhz < 66000 ? |
| 0x7fffffff : 0x81d1a47efc5cb700ull / ((uint64_t)fkhz*fkhz); |
| } |
| |
| static int interpolate_x(int expected_y, int x1, int x2, int y1, int y2) |
| { |
| return y1 == y2 ? |
| 0 : (int64_t)(expected_y - y1)*(x2 - x1) / (y2 - y1) + x1; |
| } |
| |
| static int interpolate_y(int expected_x, int x1, int x2, int y1, int y2) |
| { |
| return x1 == x2 ? |
| 0 : (int64_t)(expected_x - x1)*(y2 - y1) / (x2 - x1) + y1; |
| } |
| |
| /* this performs measurements of IF, FM and Stereo frequencies |
| * Input can be: MSS_FM, MSS_IF, MSS_SD */ |
| static int tuner_measure(unsigned char type, int scale, int duration) |
| { |
| int64_t finval; |
| |
| /* enable measuring */ |
| lv24020lp_write_set(MSRC_SEL, type); |
| lv24020lp_write_clear(CNT_CTRL, CNT_SEL); |
| lv24020lp_write_set(RADIO_CTRL1, EN_MEAS); |
| |
| /* reset counter */ |
| lv24020lp_write_set(CNT_CTRL, CNT1_CLR); |
| lv24020lp_write_clear(CNT_CTRL, CNT1_CLR); |
| |
| /* start counter, delay for specified time and stop it */ |
| lv24020lp_write_set(CNT_CTRL, CNT_EN); |
| udelay(duration*1000 - 16); |
| lv24020lp_write_clear(CNT_CTRL, CNT_EN); |
| |
| /* read tick count */ |
| finval = (lv24020lp_read(CNT_H) << 8) | lv24020lp_read(CNT_L); |
| |
| /* restore measure mode */ |
| lv24020lp_write_clear(RADIO_CTRL1, EN_MEAS); |
| lv24020lp_write_clear(MSRC_SEL, type); |
| |
| /* convert value */ |
| if (type == MSS_FM) |
| finval = scale*finval*256 / duration; |
| else |
| finval = scale*finval / duration; |
| |
| /* This function takes a loooong time and other stuff needs |
| running by now */ |
| yield(); |
| |
| return (int)finval; |
| } |
| |
| /* set the FM oscillator frequency */ |
| static void set_frequency(int freq) |
| { |
| int coef, cap_value, osc_value; |
| int f1, f2, x1, x2; |
| int count; |
| |
| TUNER_LOG_OPEN(); |
| |
| TUNER_LOG("set_frequency(%d)\n", freq); |
| |
| enable_afc(false); |
| |
| /* MHz -> kHz */ |
| freq /= 1000; |
| |
| TUNER_LOG("Select cap:\n"); |
| |
| coef = calculate_coef(freq); |
| cap_value = interpolate_x(coef, sw_cap_low, sw_cap_high, |
| coef_00, coef_01); |
| |
| osc_value = sw_osc_low; |
| lv24020lp_write(FM_OSC, osc_value); |
| |
| /* Just in case - don't go into infinite loop */ |
| for (count = 0; count < 30; count++) |
| { |
| int y0 = interpolate_y(cap_value, sw_cap_low, sw_cap_high, |
| coef_00, coef_01); |
| int y1 = interpolate_y(cap_value, sw_cap_low, sw_cap_high, |
| coef_10, coef_11); |
| int coef_fcur, cap_new, coef_cor, range; |
| |
| lv24020lp_write(FM_CAP, cap_value); |
| |
| range = y1 - y0; |
| f1 = tuner_measure(MSS_FM, 1, 16); |
| coef_fcur = calculate_coef(f1); |
| coef_cor = calculate_coef((f1*1000 + 32*256) / 1000); |
| y0 = coef_cor; |
| y1 = y0 + range; |
| |
| TUNER_LOG("%d %d %d %d %d %d %d %d\n", |
| f1, cap_value, coef, coef_fcur, coef_cor, y0, y1, range); |
| |
| if (coef >= y0 && coef <= y1) |
| { |
| osc_value = interpolate_x(coef, sw_osc_low, sw_osc_high, |
| y0, y1); |
| |
| if (osc_value >= sw_osc_low && osc_value <= sw_osc_high) |
| break; |
| } |
| |
| cap_new = interpolate_x(coef, cap_value, sw_cap_high, |
| coef_fcur, coef_01); |
| |
| if (cap_new == cap_value) |
| { |
| if (coef < coef_fcur) |
| cap_value++; |
| else |
| cap_value--; |
| } |
| else |
| { |
| cap_value = cap_new; |
| } |
| } |
| |
| TUNER_LOG("osc_value: %d\n", osc_value); |
| |
| TUNER_LOG("Tune:\n"); |
| |
| x1 = sw_osc_low, x2 = sw_osc_high; |
| /* FM_OSC already at SW_OSC low and f1 is already the measured |
| frequency */ |
| |
| do |
| { |
| int x2_new; |
| |
| lv24020lp_write(FM_OSC, x2); |
| f2 = tuner_measure(MSS_FM, 1, 16); |
| |
| if (abs(f2 - freq) <= 16) |
| { |
| TUNER_LOG("%d %d %d %d\n", f1, f2, x1, x2); |
| break; |
| } |
| |
| x2_new = interpolate_x(freq, x1, x2, f1, f2); |
| |
| x1 = x2, f1 = f2, x2 = x2_new; |
| TUNER_LOG("%d %d %d %d\n", f1, f2, x1, x2); |
| } |
| while (x2 != 0); |
| |
| if (x2 == 0) |
| { |
| /* May still be close enough */ |
| TUNER_LOG("tuning failed - diff: %d\n", f2 - freq); |
| } |
| |
| enable_afc(true); |
| |
| TUNER_LOG("\n"); |
| |
| TUNER_LOG_SYNC(); |
| } |
| |
| #define TOO_SMALL (1 << 0) |
| #define TOO_BIG (1 << 1) |
| #define APPROACH_UP_1 (1 << 2) |
| #define APPROACH_DOWN_1 (1 << 3) |
| |
| static void fine_step_tune(int (*setcmp)(int regval), int regval, int step) |
| { |
| /* Registers are not always stable, timeout if best fit not found soon |
| enough */ |
| unsigned long abort = current_tick + HZ*2; |
| int flags = 0; |
| |
| while (TIME_BEFORE(current_tick, abort)) |
| { |
| int cmp; |
| |
| regval = regval + step; |
| |
| cmp = setcmp(regval); |
| |
| if (cmp == 0) |
| break; |
| |
| step = abs(step); |
| |
| if (cmp < 0) |
| { |
| flags |= TOO_SMALL; |
| if (step == 1) |
| flags |= APPROACH_UP_1; |
| } |
| else |
| { |
| step = -step; |
| flags |= TOO_BIG; |
| if (step == -1) |
| step |= APPROACH_DOWN_1; |
| } |
| |
| if ((flags & APPROACH_UP_1) && (flags & APPROACH_DOWN_1)) |
| break; /* approached with step=1: best fit value found */ |
| |
| if ((flags & TOO_SMALL) && (flags & TOO_BIG)) |
| { |
| step /= 2; |
| if (step == 0) |
| step = 1; |
| flags &= ~(TOO_SMALL | TOO_BIG); |
| } |
| } |
| } |
| |
| static int if_setcmp(int regval) |
| { |
| lv24020lp_write(IF_OSC, regval); |
| lv24020lp_write(IF_CENTER, regval); |
| lv24020lp_write(IF_BW, 65*regval/100); |
| |
| if_set = tuner_measure(MSS_IF, 1000, 32); |
| |
| /* This register is bounces around by a few hundred Hz and doesn't seem |
| to be precisely tuneable. Just do 110000 +/- 500 since it's not very |
| critical it seems. */ |
| if (abs(if_set - 110000) <= 500) |
| return 0; |
| |
| return if_set < 110000 ? -1 : 1; |
| } |
| |
| static int sd_setcmp(int regval) |
| { |
| lv24020lp_write(SD_OSC, regval); |
| |
| sd_set = tuner_measure(MSS_SD, 1000, 32); |
| |
| if (abs(sd_set - 38300) <= 31) |
| return 0; |
| |
| return sd_set < 38300 ? -1 : 1; |
| } |
| |
| static void set_sleep(bool sleep) |
| { |
| if (sleep || tuner_awake()) |
| return; |
| |
| if ((tuner_status & (TUNER_PRESENT | TUNER_POWERED)) != |
| (TUNER_PRESENT | TUNER_POWERED)) |
| return; |
| |
| enable_afc(false); |
| |
| /* 2. Calibrate the IF frequency at 110 kHz: */ |
| lv24020lp_write_clear(RADIO_CTRL2, IF_PM_L); |
| fine_step_tune(if_setcmp, 0x80, 8); |
| lv24020lp_write_set(RADIO_CTRL2, IF_PM_L); |
| |
| /* 3. Calibrate the stereo decoder clock at 38.3 kHz: */ |
| lv24020lp_write_set(STEREO_CTRL, SD_PM); |
| fine_step_tune(sd_setcmp, 0x80, 8); |
| lv24020lp_write_clear(STEREO_CTRL, SD_PM); |
| |
| /* calculate FM tuning coefficients */ |
| lv24020lp_write(FM_CAP, sw_cap_low); |
| lv24020lp_write(FM_OSC, sw_osc_low); |
| coef_00 = calculate_coef(tuner_measure(MSS_FM, 1, 64)); |
| |
| lv24020lp_write(FM_CAP, sw_cap_high); |
| coef_01 = calculate_coef(tuner_measure(MSS_FM, 1, 64)); |
| |
| lv24020lp_write(FM_CAP, sw_cap_low); |
| lv24020lp_write(FM_OSC, sw_osc_high); |
| coef_10 = calculate_coef(tuner_measure(MSS_FM, 1, 64)); |
| |
| lv24020lp_write(FM_CAP, sw_cap_high); |
| coef_11 = calculate_coef(tuner_measure(MSS_FM, 1, 64)); |
| |
| /* set various audio level settings */ |
| lv24020lp_write(AUDIO_CTRL1, TONE_LVL_SET(0) | VOL_LVL_SET(0)); |
| lv24020lp_write_set(RADIO_CTRL2, AGCSP); |
| lv24020lp_write_set(RADIO_CTRL3, VOLSH); |
| lv24020lp_write(STEREO_CTRL, FMCS_SET(7) | AUTOSSR); |
| lv24020lp_write(PW_SCTRL, SS_CTRL_SET(3) | SM_CTRL_SET(1) | |
| PW_RAD); |
| |
| tuner_status |= TUNER_AWAKE; |
| } |
| |
| static int lp24020lp_tuned(void) |
| { |
| return RSS_FS(lv24020lp_read(RADIO_STAT)) < 0x1f; |
| } |
| |
| static int lv24020lp_debug_info(int setting) |
| { |
| int val = -1; |
| |
| if (setting >= LV24020LP_DEBUG_FIRST && setting <= LV24020LP_DEBUG_LAST) |
| { |
| val = 0; |
| |
| if (tuner_awake()) |
| { |
| switch (setting) |
| { |
| /* tuner-specific debug info */ |
| case LV24020LP_CTRL_STAT: |
| val = lv24020lp_read(CTRL_STAT); |
| break; |
| |
| case LV24020LP_REG_STAT: |
| val = lv24020lp_read(RADIO_STAT); |
| break; |
| |
| case LV24020LP_MSS_FM: |
| val = tuner_measure(MSS_FM, 1, 16); |
| break; |
| |
| case LV24020LP_MSS_IF: |
| val = tuner_measure(MSS_IF, 1000, 16); |
| break; |
| |
| case LV24020LP_MSS_SD: |
| val = tuner_measure(MSS_SD, 1000, 16); |
| break; |
| |
| case LV24020LP_IF_SET: |
| val = if_set; |
| break; |
| |
| case LV24020LP_SD_SET: |
| val = sd_set; |
| break; |
| } |
| } |
| } |
| |
| return val; |
| } |
| |
| /** Public interfaces **/ |
| void lv24020lp_init(void) |
| { |
| mutex_init(&tuner_mtx); |
| } |
| |
| void lv24020lp_lock(void) |
| { |
| mutex_lock(&tuner_mtx); |
| } |
| |
| void lv24020lp_unlock(void) |
| { |
| mutex_unlock(&tuner_mtx); |
| } |
| |
| /* This function expects the driver to be locked externally */ |
| void lv24020lp_power(bool status) |
| { |
| static const unsigned char tuner_defaults[][2] = |
| { |
| /* Block 1 writeable registers */ |
| { MSRC_SEL, AFC_LVL }, |
| { FM_OSC, 0x80 }, |
| { SD_OSC, 0x80 }, |
| { IF_OSC, 0x80 }, |
| { CNT_CTRL, CNT1_CLR | SWP_CNT_L }, |
| { IRQ_MSK, 0x00 }, /* IRQ_LVL -> Low to High */ |
| { FM_CAP, 0x80 }, |
| /* { IRQ_OUT, 0x00 }, No action on this register (skip) */ |
| /* Block 2 writeable registers */ |
| { RADIO_CTRL1, EN_AFC }, |
| { IF_CENTER, 0x80 }, |
| { IF_BW, 65*0x80 / 100 }, /* 65% of IF_OSC */ |
| { RADIO_CTRL2, IF_PM_L }, |
| { RADIO_CTRL3, AGC_SLVL | SE_FM }, |
| { STEREO_CTRL, FMCS_SET(4) | AUTOSSR }, |
| { AUDIO_CTRL1, TONE_LVL_SET(7) | VOL_LVL_SET(7) }, |
| { AUDIO_CTRL2, BPFREQ_HIGH }, /* deemphasis 50us */ |
| { PW_SCTRL, SS_CTRL_SET(3) | SM_CTRL_SET(3) | PW_RAD }, |
| }; |
| |
| unsigned i; |
| |
| if (status) |
| { |
| tuner_status |= (TUNER_PRESENCE_CHECKED | TUNER_POWERED); |
| |
| /* if tuner is present, CHIP ID is 0x09 */ |
| if (lv24020lp_read(CHIP_ID) == 0x09) |
| { |
| tuner_status |= TUNER_PRESENT; |
| |
| /* After power-up, the LV2400x needs to be initialized as |
| follows: */ |
| |
| /* 1. Write default values to the registers: */ |
| lv24020lp_regs[BLK_SEL] = 0; /* Force a switch on the first */ |
| for (i = 0; i < ARRAYLEN(tuner_defaults); i++) |
| lv24020lp_write(tuner_defaults[i][0], tuner_defaults[i][1]); |
| |
| /* Complete the startup calibration if the tuner is woken */ |
| sleep(HZ/10); |
| } |
| } |
| else |
| { |
| /* Power off */ |
| if (tuner_status & TUNER_PRESENT) |
| lv24020lp_write_clear(PW_SCTRL, PW_RAD); |
| |
| tuner_status &= ~(TUNER_POWERED | TUNER_AWAKE); |
| } |
| } |
| |
| int lv24020lp_set(int setting, int value) |
| { |
| int val = 1; |
| |
| mutex_lock(&tuner_mtx); |
| |
| switch(setting) |
| { |
| case RADIO_SLEEP: |
| set_sleep(value); |
| break; |
| |
| case RADIO_FREQUENCY: |
| set_frequency(value); |
| break; |
| |
| case RADIO_SCAN_FREQUENCY: |
| /* TODO: really implement this */ |
| set_frequency(value); |
| val = lp24020lp_tuned(); |
| break; |
| |
| case RADIO_MUTE: |
| if (value) |
| lv24020lp_write_clear(RADIO_CTRL3, AMUTE_L); |
| else |
| lv24020lp_write_set(RADIO_CTRL3, AMUTE_L); |
| break; |
| |
| case RADIO_REGION: |
| if (lv24020lp_region_data[value]) |
| lv24020lp_write_set(AUDIO_CTRL2, DEEMP); |
| else |
| lv24020lp_write_clear(AUDIO_CTRL2, DEEMP); |
| break; |
| |
| case RADIO_FORCE_MONO: |
| if (value) |
| lv24020lp_write_set(STEREO_CTRL, ST_M); |
| else |
| lv24020lp_write_clear(STEREO_CTRL, ST_M); |
| break; |
| |
| default: |
| value = -1; |
| } |
| |
| mutex_unlock(&tuner_mtx); |
| |
| return val; |
| } |
| |
| int lv24020lp_get(int setting) |
| { |
| int val = -1; |
| |
| mutex_lock(&tuner_mtx); |
| |
| switch(setting) |
| { |
| case RADIO_TUNED: |
| /* TODO: really implement this */ |
| val = lp24020lp_tuned(); |
| break; |
| |
| case RADIO_STEREO: |
| val = (lv24020lp_read(RADIO_STAT) & RSS_MS) != 0; |
| break; |
| |
| case RADIO_PRESENT: |
| { |
| bool fmstatus = true; |
| |
| if (!(tuner_status & TUNER_PRESENCE_CHECKED)) |
| fmstatus = tuner_power(true); |
| |
| val = (tuner_status & TUNER_PRESENT) != 0; |
| |
| if (!fmstatus) |
| tuner_power(false); |
| break; |
| } |
| |
| default: |
| val = lv24020lp_debug_info(setting); |
| } |
| |
| mutex_unlock(&tuner_mtx); |
| |
| return val; |
| } |
| #endif /* BOOTLOADER */ |