414 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * FM Driver for Connectivity chip of Texas Instruments.4 * This sub-module of FM driver implements FM TX functionality.5 *6 * Copyright (C) 2011 Texas Instruments7 */8 9#include <linux/delay.h>10#include "fmdrv.h"11#include "fmdrv_common.h"12#include "fmdrv_tx.h"13 14int fm_tx_set_stereo_mono(struct fmdev *fmdev, u16 mode)15{16 u16 payload;17 int ret;18 19 if (fmdev->tx_data.aud_mode == mode)20 return 0;21 22 fmdbg("stereo mode: %d\n", mode);23 24 /* Set Stereo/Mono mode */25 payload = (1 - mode);26 ret = fmc_send_cmd(fmdev, MONO_SET, REG_WR, &payload,27 sizeof(payload), NULL, NULL);28 if (ret < 0)29 return ret;30 31 fmdev->tx_data.aud_mode = mode;32 33 return ret;34}35 36static int set_rds_text(struct fmdev *fmdev, u8 *rds_text)37{38 u16 payload;39 int ret;40 41 ret = fmc_send_cmd(fmdev, RDS_DATA_SET, REG_WR, rds_text,42 strlen(rds_text), NULL, NULL);43 if (ret < 0)44 return ret;45 46 /* Scroll mode */47 payload = (u16)0x1;48 ret = fmc_send_cmd(fmdev, DISPLAY_MODE, REG_WR, &payload,49 sizeof(payload), NULL, NULL);50 if (ret < 0)51 return ret;52 53 return 0;54}55 56static int set_rds_data_mode(struct fmdev *fmdev, u8 mode)57{58 u16 payload;59 int ret;60 61 /* Setting unique PI TODO: how unique? */62 payload = (u16)0xcafe;63 ret = fmc_send_cmd(fmdev, PI_SET, REG_WR, &payload,64 sizeof(payload), NULL, NULL);65 if (ret < 0)66 return ret;67 68 /* Set decoder id */69 payload = (u16)0xa;70 ret = fmc_send_cmd(fmdev, DI_SET, REG_WR, &payload,71 sizeof(payload), NULL, NULL);72 if (ret < 0)73 return ret;74 75 /* TODO: RDS_MODE_GET? */76 return 0;77}78 79static int set_rds_len(struct fmdev *fmdev, u8 type, u16 len)80{81 u16 payload;82 int ret;83 84 len |= type << 8;85 payload = len;86 ret = fmc_send_cmd(fmdev, RDS_CONFIG_DATA_SET, REG_WR, &payload,87 sizeof(payload), NULL, NULL);88 if (ret < 0)89 return ret;90 91 /* TODO: LENGTH_GET? */92 return 0;93}94 95int fm_tx_set_rds_mode(struct fmdev *fmdev, u8 rds_en_dis)96{97 u16 payload;98 int ret;99 u8 rds_text[] = "Zoom2\n";100 101 fmdbg("rds_en_dis:%d(E:%d, D:%d)\n", rds_en_dis,102 FM_RDS_ENABLE, FM_RDS_DISABLE);103 104 if (rds_en_dis == FM_RDS_ENABLE) {105 /* Set RDS length */106 set_rds_len(fmdev, 0, strlen(rds_text));107 108 /* Set RDS text */109 set_rds_text(fmdev, rds_text);110 111 /* Set RDS mode */112 set_rds_data_mode(fmdev, 0x0);113 }114 115 /* Send command to enable RDS */116 if (rds_en_dis == FM_RDS_ENABLE)117 payload = 0x01;118 else119 payload = 0x00;120 121 ret = fmc_send_cmd(fmdev, RDS_DATA_ENB, REG_WR, &payload,122 sizeof(payload), NULL, NULL);123 if (ret < 0)124 return ret;125 126 if (rds_en_dis == FM_RDS_ENABLE) {127 /* Set RDS length */128 set_rds_len(fmdev, 0, strlen(rds_text));129 130 /* Set RDS text */131 set_rds_text(fmdev, rds_text);132 }133 fmdev->tx_data.rds.flag = rds_en_dis;134 135 return 0;136}137 138int fm_tx_set_radio_text(struct fmdev *fmdev, u8 *rds_text, u8 rds_type)139{140 u16 payload;141 int ret;142 143 if (fmdev->curr_fmmode != FM_MODE_TX)144 return -EPERM;145 146 fm_tx_set_rds_mode(fmdev, 0);147 148 /* Set RDS length */149 set_rds_len(fmdev, rds_type, strlen(rds_text));150 151 /* Set RDS text */152 set_rds_text(fmdev, rds_text);153 154 /* Set RDS mode */155 set_rds_data_mode(fmdev, 0x0);156 157 payload = 1;158 ret = fmc_send_cmd(fmdev, RDS_DATA_ENB, REG_WR, &payload,159 sizeof(payload), NULL, NULL);160 if (ret < 0)161 return ret;162 163 return 0;164}165 166int fm_tx_set_af(struct fmdev *fmdev, u32 af)167{168 u16 payload;169 int ret;170 171 if (fmdev->curr_fmmode != FM_MODE_TX)172 return -EPERM;173 174 fmdbg("AF: %d\n", af);175 176 af = (af - 87500) / 100;177 payload = (u16)af;178 ret = fmc_send_cmd(fmdev, TA_SET, REG_WR, &payload,179 sizeof(payload), NULL, NULL);180 if (ret < 0)181 return ret;182 183 return 0;184}185 186int fm_tx_set_region(struct fmdev *fmdev, u8 region)187{188 u16 payload;189 int ret;190 191 if (region != FM_BAND_EUROPE_US && region != FM_BAND_JAPAN) {192 fmerr("Invalid band\n");193 return -EINVAL;194 }195 196 /* Send command to set the band */197 payload = (u16)region;198 ret = fmc_send_cmd(fmdev, TX_BAND_SET, REG_WR, &payload,199 sizeof(payload), NULL, NULL);200 if (ret < 0)201 return ret;202 203 return 0;204}205 206int fm_tx_set_mute_mode(struct fmdev *fmdev, u8 mute_mode_toset)207{208 u16 payload;209 int ret;210 211 fmdbg("tx: mute mode %d\n", mute_mode_toset);212 213 payload = mute_mode_toset;214 ret = fmc_send_cmd(fmdev, MUTE, REG_WR, &payload,215 sizeof(payload), NULL, NULL);216 if (ret < 0)217 return ret;218 219 return 0;220}221 222/* Set TX Audio I/O */223static int set_audio_io(struct fmdev *fmdev)224{225 struct fmtx_data *tx = &fmdev->tx_data;226 u16 payload;227 int ret;228 229 /* Set Audio I/O Enable */230 payload = tx->audio_io;231 ret = fmc_send_cmd(fmdev, AUDIO_IO_SET, REG_WR, &payload,232 sizeof(payload), NULL, NULL);233 if (ret < 0)234 return ret;235 236 /* TODO: is audio set? */237 return 0;238}239 240/* Start TX Transmission */241static int enable_xmit(struct fmdev *fmdev, u8 new_xmit_state)242{243 struct fmtx_data *tx = &fmdev->tx_data;244 unsigned long timeleft;245 u16 payload;246 int ret;247 248 /* Enable POWER_ENB interrupts */249 payload = FM_POW_ENB_EVENT;250 ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,251 sizeof(payload), NULL, NULL);252 if (ret < 0)253 return ret;254 255 /* Set Power Enable */256 payload = new_xmit_state;257 ret = fmc_send_cmd(fmdev, POWER_ENB_SET, REG_WR, &payload,258 sizeof(payload), NULL, NULL);259 if (ret < 0)260 return ret;261 262 /* Wait for Power Enabled */263 init_completion(&fmdev->maintask_comp);264 timeleft = wait_for_completion_timeout(&fmdev->maintask_comp,265 FM_DRV_TX_TIMEOUT);266 if (!timeleft) {267 fmerr("Timeout(%d sec),didn't get tune ended interrupt\n",268 jiffies_to_msecs(FM_DRV_TX_TIMEOUT) / 1000);269 return -ETIMEDOUT;270 }271 272 set_bit(FM_CORE_TX_XMITING, &fmdev->flag);273 tx->xmit_state = new_xmit_state;274 275 return 0;276}277 278/* Set TX power level */279int fm_tx_set_pwr_lvl(struct fmdev *fmdev, u8 new_pwr_lvl)280{281 u16 payload;282 struct fmtx_data *tx = &fmdev->tx_data;283 int ret;284 285 if (fmdev->curr_fmmode != FM_MODE_TX)286 return -EPERM;287 fmdbg("tx: pwr_level_to_set %ld\n", (long int)new_pwr_lvl);288 289 /* If the core isn't ready update global variable */290 if (!test_bit(FM_CORE_READY, &fmdev->flag)) {291 tx->pwr_lvl = new_pwr_lvl;292 return 0;293 }294 295 /* Set power level: Application will specify power level value in296 * units of dB/uV, whereas range and step are specific to FM chip.297 * For TI's WL chips, convert application specified power level value298 * to chip specific value by subtracting 122 from it. Refer to TI FM299 * data sheet for details.300 * */301 302 payload = (FM_PWR_LVL_HIGH - new_pwr_lvl);303 ret = fmc_send_cmd(fmdev, POWER_LEV_SET, REG_WR, &payload,304 sizeof(payload), NULL, NULL);305 if (ret < 0)306 return ret;307 308 /* TODO: is the power level set? */309 tx->pwr_lvl = new_pwr_lvl;310 311 return 0;312}313 314/*315 * Sets FM TX pre-emphasis filter value (OFF, 50us, or 75us)316 * Convert V4L2 specified filter values to chip specific filter values.317 */318int fm_tx_set_preemph_filter(struct fmdev *fmdev, u32 preemphasis)319{320 struct fmtx_data *tx = &fmdev->tx_data;321 u16 payload;322 int ret;323 324 if (fmdev->curr_fmmode != FM_MODE_TX)325 return -EPERM;326 327 switch (preemphasis) {328 case V4L2_PREEMPHASIS_DISABLED:329 payload = FM_TX_PREEMPH_OFF;330 break;331 case V4L2_PREEMPHASIS_50_uS:332 payload = FM_TX_PREEMPH_50US;333 break;334 case V4L2_PREEMPHASIS_75_uS:335 payload = FM_TX_PREEMPH_75US;336 break;337 }338 339 ret = fmc_send_cmd(fmdev, PREMPH_SET, REG_WR, &payload,340 sizeof(payload), NULL, NULL);341 if (ret < 0)342 return ret;343 344 tx->preemph = payload;345 346 return ret;347}348 349/* Get the TX tuning capacitor value.*/350int fm_tx_get_tune_cap_val(struct fmdev *fmdev)351{352 u16 curr_val;353 u32 resp_len;354 int ret;355 356 if (fmdev->curr_fmmode != FM_MODE_TX)357 return -EPERM;358 359 ret = fmc_send_cmd(fmdev, READ_FMANT_TUNE_VALUE, REG_RD,360 NULL, sizeof(curr_val), &curr_val, &resp_len);361 if (ret < 0)362 return ret;363 364 curr_val = be16_to_cpu((__force __be16)curr_val);365 366 return curr_val;367}368 369/* Set TX Frequency */370int fm_tx_set_freq(struct fmdev *fmdev, u32 freq_to_set)371{372 struct fmtx_data *tx = &fmdev->tx_data;373 u16 payload, chanl_index;374 int ret;375 376 if (test_bit(FM_CORE_TX_XMITING, &fmdev->flag)) {377 enable_xmit(fmdev, 0);378 clear_bit(FM_CORE_TX_XMITING, &fmdev->flag);379 }380 381 /* Enable FR, BL interrupts */382 payload = (FM_FR_EVENT | FM_BL_EVENT);383 ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,384 sizeof(payload), NULL, NULL);385 if (ret < 0)386 return ret;387 388 tx->tx_frq = (unsigned long)freq_to_set;389 fmdbg("tx: freq_to_set %ld\n", (long int)tx->tx_frq);390 391 chanl_index = freq_to_set / 10;392 393 /* Set current tuner channel */394 payload = chanl_index;395 ret = fmc_send_cmd(fmdev, CHANL_SET, REG_WR, &payload,396 sizeof(payload), NULL, NULL);397 if (ret < 0)398 return ret;399 400 fm_tx_set_pwr_lvl(fmdev, tx->pwr_lvl);401 fm_tx_set_preemph_filter(fmdev, tx->preemph);402 403 tx->audio_io = 0x01; /* I2S */404 set_audio_io(fmdev);405 406 enable_xmit(fmdev, 0x01); /* Enable transmission */407 408 tx->aud_mode = FM_STEREO_MODE;409 tx->rds.flag = FM_RDS_DISABLE;410 411 return 0;412}413 414