| /* |
| * FM Driver for Connectivity chip of Texas Instruments. |
| * This sub-module of FM driver implements FM RX functionality. |
| * |
| * Copyright (C) 2011 Texas Instruments |
| * Author: Raja Mani <raja_mani@ti.com> |
| * Author: Manjunatha Halli <manjunatha_halli@ti.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| #include "fmdrv.h" |
| #include "fmdrv_common.h" |
| #include "fmdrv_rx.h" |
| |
| void fm_rx_reset_rds_cache(struct fmdev *fmdev) |
| { |
| fmdev->rx.rds.flag = FM_RDS_DISABLE; |
| fmdev->rx.rds.last_blk_idx = 0; |
| fmdev->rx.rds.wr_idx = 0; |
| fmdev->rx.rds.rd_idx = 0; |
| |
| if (fmdev->rx.af_mode == FM_RX_RDS_AF_SWITCH_MODE_ON) |
| fmdev->irq_info.mask |= FM_LEV_EVENT; |
| } |
| |
| void fm_rx_reset_station_info(struct fmdev *fmdev) |
| { |
| fmdev->rx.stat_info.picode = FM_NO_PI_CODE; |
| fmdev->rx.stat_info.afcache_size = 0; |
| fmdev->rx.stat_info.af_list_max = 0; |
| } |
| |
| int fm_rx_set_freq(struct fmdev *fmdev, u32 freq) |
| { |
| unsigned long timeleft; |
| u16 payload, curr_frq, intr_flag; |
| u32 curr_frq_in_khz; |
| u32 resp_len; |
| int ret; |
| |
| if (freq < fmdev->rx.region.bot_freq || freq > fmdev->rx.region.top_freq) { |
| fmerr("Invalid frequency %d\n", freq); |
| return -EINVAL; |
| } |
| |
| /* Set audio enable */ |
| payload = FM_RX_AUDIO_ENABLE_I2S_AND_ANALOG; |
| |
| ret = fmc_send_cmd(fmdev, AUDIO_ENABLE_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Set hilo to automatic selection */ |
| payload = FM_RX_IFFREQ_HILO_AUTOMATIC; |
| ret = fmc_send_cmd(fmdev, HILO_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Calculate frequency index and set*/ |
| payload = (freq - fmdev->rx.region.bot_freq) / FM_FREQ_MUL; |
| |
| ret = fmc_send_cmd(fmdev, FREQ_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Read flags - just to clear any pending interrupts if we had */ |
| ret = fmc_send_cmd(fmdev, FLAG_GET, REG_RD, NULL, 2, NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Enable FR, BL interrupts */ |
| intr_flag = fmdev->irq_info.mask; |
| fmdev->irq_info.mask = (FM_FR_EVENT | FM_BL_EVENT); |
| payload = fmdev->irq_info.mask; |
| ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Start tune */ |
| payload = FM_TUNER_PRESET_MODE; |
| ret = fmc_send_cmd(fmdev, TUNER_MODE_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| goto exit; |
| |
| /* Wait for tune ended interrupt */ |
| init_completion(&fmdev->maintask_comp); |
| timeleft = wait_for_completion_timeout(&fmdev->maintask_comp, |
| FM_DRV_TX_TIMEOUT); |
| if (!timeleft) { |
| fmerr("Timeout(%d sec),didn't get tune ended int\n", |
| jiffies_to_msecs(FM_DRV_TX_TIMEOUT) / 1000); |
| ret = -ETIMEDOUT; |
| goto exit; |
| } |
| |
| /* Read freq back to confirm */ |
| ret = fmc_send_cmd(fmdev, FREQ_SET, REG_RD, NULL, 2, &curr_frq, &resp_len); |
| if (ret < 0) |
| goto exit; |
| |
| curr_frq = be16_to_cpu((__force __be16)curr_frq); |
| curr_frq_in_khz = (fmdev->rx.region.bot_freq + ((u32)curr_frq * FM_FREQ_MUL)); |
| |
| if (curr_frq_in_khz != freq) { |
| pr_info("Frequency is set to (%d) but requested freq is (%d)\n", |
| curr_frq_in_khz, freq); |
| } |
| |
| /* Update local cache */ |
| fmdev->rx.freq = curr_frq_in_khz; |
| exit: |
| /* Re-enable default FM interrupts */ |
| fmdev->irq_info.mask = intr_flag; |
| payload = fmdev->irq_info.mask; |
| ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Reset RDS cache and current station pointers */ |
| fm_rx_reset_rds_cache(fmdev); |
| fm_rx_reset_station_info(fmdev); |
| |
| return ret; |
| } |
| |
| static int fm_rx_set_channel_spacing(struct fmdev *fmdev, u32 spacing) |
| { |
| u16 payload; |
| int ret; |
| |
| if (spacing > 0 && spacing <= 50000) |
| spacing = FM_CHANNEL_SPACING_50KHZ; |
| else if (spacing > 50000 && spacing <= 100000) |
| spacing = FM_CHANNEL_SPACING_100KHZ; |
| else |
| spacing = FM_CHANNEL_SPACING_200KHZ; |
| |
| /* set channel spacing */ |
| payload = spacing; |
| ret = fmc_send_cmd(fmdev, CHANL_BW_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| fmdev->rx.region.chanl_space = spacing * FM_FREQ_MUL; |
| |
| return ret; |
| } |
| |
| int fm_rx_seek(struct fmdev *fmdev, u32 seek_upward, |
| u32 wrap_around, u32 spacing) |
| { |
| u32 resp_len; |
| u16 curr_frq, next_frq, last_frq; |
| u16 payload, int_reason, intr_flag; |
| u16 offset, space_idx; |
| unsigned long timeleft; |
| int ret; |
| |
| /* Set channel spacing */ |
| ret = fm_rx_set_channel_spacing(fmdev, spacing); |
| if (ret < 0) { |
| fmerr("Failed to set channel spacing\n"); |
| return ret; |
| } |
| |
| /* Read the current frequency from chip */ |
| ret = fmc_send_cmd(fmdev, FREQ_SET, REG_RD, NULL, |
| sizeof(curr_frq), &curr_frq, &resp_len); |
| if (ret < 0) |
| return ret; |
| |
| curr_frq = be16_to_cpu((__force __be16)curr_frq); |
| last_frq = (fmdev->rx.region.top_freq - fmdev->rx.region.bot_freq) / FM_FREQ_MUL; |
| |
| /* Check the offset in order to be aligned to the channel spacing*/ |
| space_idx = fmdev->rx.region.chanl_space / FM_FREQ_MUL; |
| offset = curr_frq % space_idx; |
| |
| next_frq = seek_upward ? curr_frq + space_idx /* Seek Up */ : |
| curr_frq - space_idx /* Seek Down */ ; |
| |
| /* |
| * Add or subtract offset in order to stay aligned to the channel |
| * spacing. |
| */ |
| if ((short)next_frq < 0) |
| next_frq = last_frq - offset; |
| else if (next_frq > last_frq) |
| next_frq = 0 + offset; |
| |
| again: |
| /* Set calculated next frequency to perform seek */ |
| payload = next_frq; |
| ret = fmc_send_cmd(fmdev, FREQ_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Set search direction (0:Seek Down, 1:Seek Up) */ |
| payload = (seek_upward ? FM_SEARCH_DIRECTION_UP : FM_SEARCH_DIRECTION_DOWN); |
| ret = fmc_send_cmd(fmdev, SEARCH_DIR_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Read flags - just to clear any pending interrupts if we had */ |
| ret = fmc_send_cmd(fmdev, FLAG_GET, REG_RD, NULL, 2, NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Enable FR, BL interrupts */ |
| intr_flag = fmdev->irq_info.mask; |
| fmdev->irq_info.mask = (FM_FR_EVENT | FM_BL_EVENT); |
| payload = fmdev->irq_info.mask; |
| ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Start seek */ |
| payload = FM_TUNER_AUTONOMOUS_SEARCH_MODE; |
| ret = fmc_send_cmd(fmdev, TUNER_MODE_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Wait for tune ended/band limit reached interrupt */ |
| init_completion(&fmdev->maintask_comp); |
| timeleft = wait_for_completion_timeout(&fmdev->maintask_comp, |
| FM_DRV_RX_SEEK_TIMEOUT); |
| if (!timeleft) { |
| fmerr("Timeout(%d sec),didn't get tune ended int\n", |
| jiffies_to_msecs(FM_DRV_RX_SEEK_TIMEOUT) / 1000); |
| return -ENODATA; |
| } |
| |
| int_reason = fmdev->irq_info.flag & (FM_TUNE_COMPLETE | FM_BAND_LIMIT); |
| |
| /* Re-enable default FM interrupts */ |
| fmdev->irq_info.mask = intr_flag; |
| payload = fmdev->irq_info.mask; |
| ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| if (int_reason & FM_BL_EVENT) { |
| if (wrap_around == 0) { |
| fmdev->rx.freq = seek_upward ? |
| fmdev->rx.region.top_freq : |
| fmdev->rx.region.bot_freq; |
| } else { |
| fmdev->rx.freq = seek_upward ? |
| fmdev->rx.region.bot_freq : |
| fmdev->rx.region.top_freq; |
| /* Calculate frequency index to write */ |
| next_frq = (fmdev->rx.freq - |
| fmdev->rx.region.bot_freq) / FM_FREQ_MUL; |
| goto again; |
| } |
| } else { |
| /* Read freq to know where operation tune operation stopped */ |
| ret = fmc_send_cmd(fmdev, FREQ_SET, REG_RD, NULL, 2, |
| &curr_frq, &resp_len); |
| if (ret < 0) |
| return ret; |
| |
| curr_frq = be16_to_cpu((__force __be16)curr_frq); |
| fmdev->rx.freq = (fmdev->rx.region.bot_freq + |
| ((u32)curr_frq * FM_FREQ_MUL)); |
| |
| } |
| /* Reset RDS cache and current station pointers */ |
| fm_rx_reset_rds_cache(fmdev); |
| fm_rx_reset_station_info(fmdev); |
| |
| return ret; |
| } |
| |
| int fm_rx_set_volume(struct fmdev *fmdev, u16 vol_to_set) |
| { |
| u16 payload; |
| int ret; |
| |
| if (fmdev->curr_fmmode != FM_MODE_RX) |
| return -EPERM; |
| |
| if (vol_to_set > FM_RX_VOLUME_MAX) { |
| fmerr("Volume is not within(%d-%d) range\n", |
| FM_RX_VOLUME_MIN, FM_RX_VOLUME_MAX); |
| return -EINVAL; |
| } |
| vol_to_set *= FM_RX_VOLUME_GAIN_STEP; |
| |
| payload = vol_to_set; |
| ret = fmc_send_cmd(fmdev, VOLUME_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| fmdev->rx.volume = vol_to_set; |
| return ret; |
| } |
| |
| /* Get volume */ |
| int fm_rx_get_volume(struct fmdev *fmdev, u16 *curr_vol) |
| { |
| if (fmdev->curr_fmmode != FM_MODE_RX) |
| return -EPERM; |
| |
| if (curr_vol == NULL) { |
| fmerr("Invalid memory\n"); |
| return -ENOMEM; |
| } |
| |
| *curr_vol = fmdev->rx.volume / FM_RX_VOLUME_GAIN_STEP; |
| |
| return 0; |
| } |
| |
| /* To get current band's bottom and top frequency */ |
| int fm_rx_get_band_freq_range(struct fmdev *fmdev, u32 *bot_freq, u32 *top_freq) |
| { |
| if (bot_freq != NULL) |
| *bot_freq = fmdev->rx.region.bot_freq; |
| |
| if (top_freq != NULL) |
| *top_freq = fmdev->rx.region.top_freq; |
| |
| return 0; |
| } |
| |
| /* Returns current band index (0-Europe/US; 1-Japan) */ |
| void fm_rx_get_region(struct fmdev *fmdev, u8 *region) |
| { |
| *region = fmdev->rx.region.fm_band; |
| } |
| |
| /* Sets band (0-Europe/US; 1-Japan) */ |
| int fm_rx_set_region(struct fmdev *fmdev, u8 region_to_set) |
| { |
| u16 payload; |
| u32 new_frq = 0; |
| int ret; |
| |
| if (region_to_set != FM_BAND_EUROPE_US && |
| region_to_set != FM_BAND_JAPAN) { |
| fmerr("Invalid band\n"); |
| return -EINVAL; |
| } |
| |
| if (fmdev->rx.region.fm_band == region_to_set) { |
| fmerr("Requested band is already configured\n"); |
| return 0; |
| } |
| |
| /* Send cmd to set the band */ |
| payload = (u16)region_to_set; |
| ret = fmc_send_cmd(fmdev, BAND_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| fmc_update_region_info(fmdev, region_to_set); |
| |
| /* Check whether current RX frequency is within band boundary */ |
| if (fmdev->rx.freq < fmdev->rx.region.bot_freq) |
| new_frq = fmdev->rx.region.bot_freq; |
| else if (fmdev->rx.freq > fmdev->rx.region.top_freq) |
| new_frq = fmdev->rx.region.top_freq; |
| |
| if (new_frq) { |
| fmdbg("Current freq is not within band limit boundary,switching to %d KHz\n", |
| new_frq); |
| /* Current RX frequency is not in range. So, update it */ |
| ret = fm_rx_set_freq(fmdev, new_frq); |
| } |
| |
| return ret; |
| } |
| |
| /* Reads current mute mode (Mute Off/On/Attenuate)*/ |
| int fm_rx_get_mute_mode(struct fmdev *fmdev, u8 *curr_mute_mode) |
| { |
| if (fmdev->curr_fmmode != FM_MODE_RX) |
| return -EPERM; |
| |
| if (curr_mute_mode == NULL) { |
| fmerr("Invalid memory\n"); |
| return -ENOMEM; |
| } |
| |
| *curr_mute_mode = fmdev->rx.mute_mode; |
| |
| return 0; |
| } |
| |
| static int fm_config_rx_mute_reg(struct fmdev *fmdev) |
| { |
| u16 payload, muteval; |
| int ret; |
| |
| muteval = 0; |
| switch (fmdev->rx.mute_mode) { |
| case FM_MUTE_ON: |
| muteval = FM_RX_AC_MUTE_MODE; |
| break; |
| |
| case FM_MUTE_OFF: |
| muteval = FM_RX_UNMUTE_MODE; |
| break; |
| |
| case FM_MUTE_ATTENUATE: |
| muteval = FM_RX_SOFT_MUTE_FORCE_MODE; |
| break; |
| } |
| if (fmdev->rx.rf_depend_mute == FM_RX_RF_DEPENDENT_MUTE_ON) |
| muteval |= FM_RX_RF_DEP_MODE; |
| else |
| muteval &= ~FM_RX_RF_DEP_MODE; |
| |
| payload = muteval; |
| ret = fmc_send_cmd(fmdev, MUTE_STATUS_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| return 0; |
| } |
| |
| /* Configures mute mode (Mute Off/On/Attenuate) */ |
| int fm_rx_set_mute_mode(struct fmdev *fmdev, u8 mute_mode_toset) |
| { |
| u8 org_state; |
| int ret; |
| |
| if (fmdev->rx.mute_mode == mute_mode_toset) |
| return 0; |
| |
| org_state = fmdev->rx.mute_mode; |
| fmdev->rx.mute_mode = mute_mode_toset; |
| |
| ret = fm_config_rx_mute_reg(fmdev); |
| if (ret < 0) { |
| fmdev->rx.mute_mode = org_state; |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /* Gets RF dependent soft mute mode enable/disable status */ |
| int fm_rx_get_rfdepend_softmute(struct fmdev *fmdev, u8 *curr_mute_mode) |
| { |
| if (fmdev->curr_fmmode != FM_MODE_RX) |
| return -EPERM; |
| |
| if (curr_mute_mode == NULL) { |
| fmerr("Invalid memory\n"); |
| return -ENOMEM; |
| } |
| |
| *curr_mute_mode = fmdev->rx.rf_depend_mute; |
| |
| return 0; |
| } |
| |
| /* Sets RF dependent soft mute mode */ |
| int fm_rx_set_rfdepend_softmute(struct fmdev *fmdev, u8 rfdepend_mute) |
| { |
| u8 org_state; |
| int ret; |
| |
| if (fmdev->curr_fmmode != FM_MODE_RX) |
| return -EPERM; |
| |
| if (rfdepend_mute != FM_RX_RF_DEPENDENT_MUTE_ON && |
| rfdepend_mute != FM_RX_RF_DEPENDENT_MUTE_OFF) { |
| fmerr("Invalid RF dependent soft mute\n"); |
| return -EINVAL; |
| } |
| if (fmdev->rx.rf_depend_mute == rfdepend_mute) |
| return 0; |
| |
| org_state = fmdev->rx.rf_depend_mute; |
| fmdev->rx.rf_depend_mute = rfdepend_mute; |
| |
| ret = fm_config_rx_mute_reg(fmdev); |
| if (ret < 0) { |
| fmdev->rx.rf_depend_mute = org_state; |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /* Returns the signal strength level of current channel */ |
| int fm_rx_get_rssi_level(struct fmdev *fmdev, u16 *rssilvl) |
| { |
| __be16 curr_rssi_lel; |
| u32 resp_len; |
| int ret; |
| |
| if (rssilvl == NULL) { |
| fmerr("Invalid memory\n"); |
| return -ENOMEM; |
| } |
| /* Read current RSSI level */ |
| ret = fmc_send_cmd(fmdev, RSSI_LVL_GET, REG_RD, NULL, 2, |
| &curr_rssi_lel, &resp_len); |
| if (ret < 0) |
| return ret; |
| |
| *rssilvl = be16_to_cpu(curr_rssi_lel); |
| |
| return 0; |
| } |
| |
| /* |
| * Sets the signal strength level that once reached |
| * will stop the auto search process |
| */ |
| int fm_rx_set_rssi_threshold(struct fmdev *fmdev, short rssi_lvl_toset) |
| { |
| u16 payload; |
| int ret; |
| |
| if (rssi_lvl_toset < FM_RX_RSSI_THRESHOLD_MIN || |
| rssi_lvl_toset > FM_RX_RSSI_THRESHOLD_MAX) { |
| fmerr("Invalid RSSI threshold level\n"); |
| return -EINVAL; |
| } |
| payload = (u16)rssi_lvl_toset; |
| ret = fmc_send_cmd(fmdev, SEARCH_LVL_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| fmdev->rx.rssi_threshold = rssi_lvl_toset; |
| |
| return 0; |
| } |
| |
| /* Returns current RX RSSI threshold value */ |
| int fm_rx_get_rssi_threshold(struct fmdev *fmdev, short *curr_rssi_lvl) |
| { |
| if (fmdev->curr_fmmode != FM_MODE_RX) |
| return -EPERM; |
| |
| if (curr_rssi_lvl == NULL) { |
| fmerr("Invalid memory\n"); |
| return -ENOMEM; |
| } |
| |
| *curr_rssi_lvl = fmdev->rx.rssi_threshold; |
| |
| return 0; |
| } |
| |
| /* Sets RX stereo/mono modes */ |
| int fm_rx_set_stereo_mono(struct fmdev *fmdev, u16 mode) |
| { |
| u16 payload; |
| int ret; |
| |
| if (mode != FM_STEREO_MODE && mode != FM_MONO_MODE) { |
| fmerr("Invalid mode\n"); |
| return -EINVAL; |
| } |
| |
| /* Set stereo/mono mode */ |
| payload = (u16)mode; |
| ret = fmc_send_cmd(fmdev, MOST_MODE_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Set stereo blending mode */ |
| payload = FM_STEREO_SOFT_BLEND; |
| ret = fmc_send_cmd(fmdev, MOST_BLEND_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| return 0; |
| } |
| |
| /* Gets current RX stereo/mono mode */ |
| int fm_rx_get_stereo_mono(struct fmdev *fmdev, u16 *mode) |
| { |
| __be16 curr_mode; |
| u32 resp_len; |
| int ret; |
| |
| if (mode == NULL) { |
| fmerr("Invalid memory\n"); |
| return -ENOMEM; |
| } |
| |
| ret = fmc_send_cmd(fmdev, MOST_MODE_SET, REG_RD, NULL, 2, |
| &curr_mode, &resp_len); |
| if (ret < 0) |
| return ret; |
| |
| *mode = be16_to_cpu(curr_mode); |
| |
| return 0; |
| } |
| |
| /* Choose RX de-emphasis filter mode (50us/75us) */ |
| int fm_rx_set_deemphasis_mode(struct fmdev *fmdev, u16 mode) |
| { |
| u16 payload; |
| int ret; |
| |
| if (fmdev->curr_fmmode != FM_MODE_RX) |
| return -EPERM; |
| |
| if (mode != FM_RX_EMPHASIS_FILTER_50_USEC && |
| mode != FM_RX_EMPHASIS_FILTER_75_USEC) { |
| fmerr("Invalid rx de-emphasis mode (%d)\n", mode); |
| return -EINVAL; |
| } |
| |
| payload = mode; |
| ret = fmc_send_cmd(fmdev, DEMPH_MODE_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| fmdev->rx.deemphasis_mode = mode; |
| |
| return 0; |
| } |
| |
| /* Gets current RX de-emphasis filter mode */ |
| int fm_rx_get_deemph_mode(struct fmdev *fmdev, u16 *curr_deemphasis_mode) |
| { |
| if (fmdev->curr_fmmode != FM_MODE_RX) |
| return -EPERM; |
| |
| if (curr_deemphasis_mode == NULL) { |
| fmerr("Invalid memory\n"); |
| return -ENOMEM; |
| } |
| |
| *curr_deemphasis_mode = fmdev->rx.deemphasis_mode; |
| |
| return 0; |
| } |
| |
| /* Enable/Disable RX RDS */ |
| int fm_rx_set_rds_mode(struct fmdev *fmdev, u8 rds_en_dis) |
| { |
| u16 payload; |
| int ret; |
| |
| if (rds_en_dis != FM_RDS_ENABLE && rds_en_dis != FM_RDS_DISABLE) { |
| fmerr("Invalid rds option\n"); |
| return -EINVAL; |
| } |
| |
| if (rds_en_dis == FM_RDS_ENABLE |
| && fmdev->rx.rds.flag == FM_RDS_DISABLE) { |
| /* Turn on RX RDS and RDS circuit */ |
| payload = FM_RX_PWR_SET_FM_AND_RDS_BLK_ON; |
| ret = fmc_send_cmd(fmdev, POWER_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Clear and reset RDS FIFO */ |
| payload = FM_RX_RDS_FLUSH_FIFO; |
| ret = fmc_send_cmd(fmdev, RDS_CNTRL_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Read flags - just to clear any pending interrupts. */ |
| ret = fmc_send_cmd(fmdev, FLAG_GET, REG_RD, NULL, 2, |
| NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Set RDS FIFO threshold value */ |
| payload = FM_RX_RDS_FIFO_THRESHOLD; |
| ret = fmc_send_cmd(fmdev, RDS_MEM_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Enable RDS interrupt */ |
| fmdev->irq_info.mask |= FM_RDS_EVENT; |
| payload = fmdev->irq_info.mask; |
| ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) { |
| fmdev->irq_info.mask &= ~FM_RDS_EVENT; |
| return ret; |
| } |
| |
| /* Update our local flag */ |
| fmdev->rx.rds.flag = FM_RDS_ENABLE; |
| } else if (rds_en_dis == FM_RDS_DISABLE |
| && fmdev->rx.rds.flag == FM_RDS_ENABLE) { |
| /* Turn off RX RDS */ |
| payload = FM_RX_PWR_SET_FM_ON_RDS_OFF; |
| ret = fmc_send_cmd(fmdev, POWER_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| /* Reset RDS pointers */ |
| fmdev->rx.rds.last_blk_idx = 0; |
| fmdev->rx.rds.wr_idx = 0; |
| fmdev->rx.rds.rd_idx = 0; |
| fm_rx_reset_station_info(fmdev); |
| |
| /* Update RDS local cache */ |
| fmdev->irq_info.mask &= ~(FM_RDS_EVENT); |
| fmdev->rx.rds.flag = FM_RDS_DISABLE; |
| } |
| |
| return 0; |
| } |
| |
| /* Returns current RX RDS enable/disable status */ |
| int fm_rx_get_rds_mode(struct fmdev *fmdev, u8 *curr_rds_en_dis) |
| { |
| if (fmdev->curr_fmmode != FM_MODE_RX) |
| return -EPERM; |
| |
| if (curr_rds_en_dis == NULL) { |
| fmerr("Invalid memory\n"); |
| return -ENOMEM; |
| } |
| |
| *curr_rds_en_dis = fmdev->rx.rds.flag; |
| |
| return 0; |
| } |
| |
| /* Sets RDS operation mode (RDS/RDBS) */ |
| int fm_rx_set_rds_system(struct fmdev *fmdev, u8 rds_mode) |
| { |
| u16 payload; |
| int ret; |
| |
| if (fmdev->curr_fmmode != FM_MODE_RX) |
| return -EPERM; |
| |
| if (rds_mode != FM_RDS_SYSTEM_RDS && rds_mode != FM_RDS_SYSTEM_RBDS) { |
| fmerr("Invalid rds mode\n"); |
| return -EINVAL; |
| } |
| /* Set RDS operation mode */ |
| payload = (u16)rds_mode; |
| ret = fmc_send_cmd(fmdev, RDS_SYSTEM_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| fmdev->rx.rds_mode = rds_mode; |
| |
| return 0; |
| } |
| |
| /* Configures Alternate Frequency switch mode */ |
| int fm_rx_set_af_switch(struct fmdev *fmdev, u8 af_mode) |
| { |
| u16 payload; |
| int ret; |
| |
| if (fmdev->curr_fmmode != FM_MODE_RX) |
| return -EPERM; |
| |
| if (af_mode != FM_RX_RDS_AF_SWITCH_MODE_ON && |
| af_mode != FM_RX_RDS_AF_SWITCH_MODE_OFF) { |
| fmerr("Invalid af mode\n"); |
| return -EINVAL; |
| } |
| /* Enable/disable low RSSI interrupt based on af_mode */ |
| if (af_mode == FM_RX_RDS_AF_SWITCH_MODE_ON) |
| fmdev->irq_info.mask |= FM_LEV_EVENT; |
| else |
| fmdev->irq_info.mask &= ~FM_LEV_EVENT; |
| |
| payload = fmdev->irq_info.mask; |
| ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload, |
| sizeof(payload), NULL, NULL); |
| if (ret < 0) |
| return ret; |
| |
| fmdev->rx.af_mode = af_mode; |
| |
| return 0; |
| } |
| |
| /* Returns Alternate Frequency switch status */ |
| int fm_rx_get_af_switch(struct fmdev *fmdev, u8 *af_mode) |
| { |
| if (fmdev->curr_fmmode != FM_MODE_RX) |
| return -EPERM; |
| |
| if (af_mode == NULL) { |
| fmerr("Invalid memory\n"); |
| return -ENOMEM; |
| } |
| |
| *af_mode = fmdev->rx.af_mode; |
| |
| return 0; |
| } |