/drivers/media/video/tlg2300/pd-radio.c
C | 423 lines | 354 code | 67 blank | 2 comment | 55 complexity | 35b2dfd1cda24fb86815c71f9e2b72d2 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0, AGPL-1.0
1#include <linux/init.h> 2#include <linux/list.h> 3#include <linux/module.h> 4#include <linux/kernel.h> 5#include <linux/bitmap.h> 6#include <linux/usb.h> 7#include <linux/i2c.h> 8#include <media/v4l2-dev.h> 9#include <linux/version.h> 10#include <linux/mm.h> 11#include <linux/mutex.h> 12#include <media/v4l2-ioctl.h> 13#include <linux/sched.h> 14 15#include "pd-common.h" 16#include "vendorcmds.h" 17 18static int set_frequency(struct poseidon *p, __u32 frequency); 19static int poseidon_fm_close(struct file *filp); 20static int poseidon_fm_open(struct file *filp); 21 22#define TUNER_FREQ_MIN_FM 76000000 23#define TUNER_FREQ_MAX_FM 108000000 24 25#define MAX_PREEMPHASIS (V4L2_PREEMPHASIS_75_uS + 1) 26static int preemphasis[MAX_PREEMPHASIS] = { 27 TLG_TUNE_ASTD_NONE, /* V4L2_PREEMPHASIS_DISABLED */ 28 TLG_TUNE_ASTD_FM_EUR, /* V4L2_PREEMPHASIS_50_uS */ 29 TLG_TUNE_ASTD_FM_US, /* V4L2_PREEMPHASIS_75_uS */ 30}; 31 32static int poseidon_check_mode_radio(struct poseidon *p) 33{ 34 int ret; 35 u32 status; 36 37 set_current_state(TASK_INTERRUPTIBLE); 38 schedule_timeout(HZ/2); 39 ret = usb_set_interface(p->udev, 0, BULK_ALTERNATE_IFACE); 40 if (ret < 0) 41 goto out; 42 43 ret = set_tuner_mode(p, TLG_MODE_FM_RADIO); 44 if (ret != 0) 45 goto out; 46 47 ret = send_set_req(p, SGNL_SRC_SEL, TLG_SIG_SRC_ANTENNA, &status); 48 ret = send_set_req(p, TUNER_AUD_ANA_STD, 49 p->radio_data.pre_emphasis, &status); 50 ret |= send_set_req(p, TUNER_AUD_MODE, 51 TLG_TUNE_TVAUDIO_MODE_STEREO, &status); 52 ret |= send_set_req(p, AUDIO_SAMPLE_RATE_SEL, 53 ATV_AUDIO_RATE_48K, &status); 54 ret |= send_set_req(p, TUNE_FREQ_SELECT, TUNER_FREQ_MIN_FM, &status); 55out: 56 return ret; 57} 58 59#ifdef CONFIG_PM 60static int pm_fm_suspend(struct poseidon *p) 61{ 62 logpm(p); 63 pm_alsa_suspend(p); 64 usb_set_interface(p->udev, 0, 0); 65 msleep(300); 66 return 0; 67} 68 69static int pm_fm_resume(struct poseidon *p) 70{ 71 logpm(p); 72 poseidon_check_mode_radio(p); 73 set_frequency(p, p->radio_data.fm_freq); 74 pm_alsa_resume(p); 75 return 0; 76} 77#endif 78 79static int poseidon_fm_open(struct file *filp) 80{ 81 struct video_device *vfd = video_devdata(filp); 82 struct poseidon *p = video_get_drvdata(vfd); 83 int ret = 0; 84 85 if (!p) 86 return -1; 87 88 mutex_lock(&p->lock); 89 if (p->state & POSEIDON_STATE_DISCONNECT) { 90 ret = -ENODEV; 91 goto out; 92 } 93 94 if (p->state && !(p->state & POSEIDON_STATE_FM)) { 95 ret = -EBUSY; 96 goto out; 97 } 98 99 usb_autopm_get_interface(p->interface); 100 if (0 == p->state) { 101 /* default pre-emphasis */ 102 if (p->radio_data.pre_emphasis == 0) 103 p->radio_data.pre_emphasis = TLG_TUNE_ASTD_FM_EUR; 104 set_debug_mode(vfd, debug_mode); 105 106 ret = poseidon_check_mode_radio(p); 107 if (ret < 0) { 108 usb_autopm_put_interface(p->interface); 109 goto out; 110 } 111 p->state |= POSEIDON_STATE_FM; 112 } 113 p->radio_data.users++; 114 kref_get(&p->kref); 115 filp->private_data = p; 116out: 117 mutex_unlock(&p->lock); 118 return ret; 119} 120 121static int poseidon_fm_close(struct file *filp) 122{ 123 struct poseidon *p = filp->private_data; 124 struct radio_data *fm = &p->radio_data; 125 uint32_t status; 126 127 mutex_lock(&p->lock); 128 fm->users--; 129 if (0 == fm->users) 130 p->state &= ~POSEIDON_STATE_FM; 131 132 if (fm->is_radio_streaming && filp == p->file_for_stream) { 133 fm->is_radio_streaming = 0; 134 send_set_req(p, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP, &status); 135 } 136 usb_autopm_put_interface(p->interface); 137 mutex_unlock(&p->lock); 138 139 kref_put(&p->kref, poseidon_delete); 140 filp->private_data = NULL; 141 return 0; 142} 143 144static int vidioc_querycap(struct file *file, void *priv, 145 struct v4l2_capability *v) 146{ 147 struct poseidon *p = file->private_data; 148 149 strlcpy(v->driver, "tele-radio", sizeof(v->driver)); 150 strlcpy(v->card, "Telegent Poseidon", sizeof(v->card)); 151 usb_make_path(p->udev, v->bus_info, sizeof(v->bus_info)); 152 v->version = KERNEL_VERSION(0, 0, 1); 153 v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 154 return 0; 155} 156 157static const struct v4l2_file_operations poseidon_fm_fops = { 158 .owner = THIS_MODULE, 159 .open = poseidon_fm_open, 160 .release = poseidon_fm_close, 161 .ioctl = video_ioctl2, 162}; 163 164static int tlg_fm_vidioc_g_tuner(struct file *file, void *priv, 165 struct v4l2_tuner *vt) 166{ 167 struct tuner_fm_sig_stat_s fm_stat = {}; 168 int ret, status, count = 5; 169 struct poseidon *p = file->private_data; 170 171 if (vt->index != 0) 172 return -EINVAL; 173 174 vt->type = V4L2_TUNER_RADIO; 175 vt->capability = V4L2_TUNER_CAP_STEREO; 176 vt->rangelow = TUNER_FREQ_MIN_FM / 62500; 177 vt->rangehigh = TUNER_FREQ_MAX_FM / 62500; 178 vt->rxsubchans = V4L2_TUNER_SUB_STEREO; 179 vt->audmode = V4L2_TUNER_MODE_STEREO; 180 vt->signal = 0; 181 vt->afc = 0; 182 183 mutex_lock(&p->lock); 184 ret = send_get_req(p, TUNER_STATUS, TLG_MODE_FM_RADIO, 185 &fm_stat, &status, sizeof(fm_stat)); 186 187 while (fm_stat.sig_lock_busy && count-- && !ret) { 188 set_current_state(TASK_INTERRUPTIBLE); 189 schedule_timeout(HZ); 190 191 ret = send_get_req(p, TUNER_STATUS, TLG_MODE_FM_RADIO, 192 &fm_stat, &status, sizeof(fm_stat)); 193 } 194 mutex_unlock(&p->lock); 195 196 if (ret || status) { 197 vt->signal = 0; 198 } else if ((fm_stat.sig_present || fm_stat.sig_locked) 199 && fm_stat.sig_strength == 0) { 200 vt->signal = 0xffff; 201 } else 202 vt->signal = (fm_stat.sig_strength * 255 / 10) << 8; 203 204 return 0; 205} 206 207static int fm_get_freq(struct file *file, void *priv, 208 struct v4l2_frequency *argp) 209{ 210 struct poseidon *p = file->private_data; 211 212 argp->frequency = p->radio_data.fm_freq; 213 return 0; 214} 215 216static int set_frequency(struct poseidon *p, __u32 frequency) 217{ 218 __u32 freq ; 219 int ret, status; 220 221 mutex_lock(&p->lock); 222 223 ret = send_set_req(p, TUNER_AUD_ANA_STD, 224 p->radio_data.pre_emphasis, &status); 225 226 freq = (frequency * 125) * 500 / 1000;/* kHZ */ 227 if (freq < TUNER_FREQ_MIN_FM/1000 || freq > TUNER_FREQ_MAX_FM/1000) { 228 ret = -EINVAL; 229 goto error; 230 } 231 232 ret = send_set_req(p, TUNE_FREQ_SELECT, freq, &status); 233 if (ret < 0) 234 goto error ; 235 ret = send_set_req(p, TAKE_REQUEST, 0, &status); 236 237 set_current_state(TASK_INTERRUPTIBLE); 238 schedule_timeout(HZ/4); 239 if (!p->radio_data.is_radio_streaming) { 240 ret = send_set_req(p, TAKE_REQUEST, 0, &status); 241 ret = send_set_req(p, PLAY_SERVICE, 242 TLG_TUNE_PLAY_SVC_START, &status); 243 p->radio_data.is_radio_streaming = 1; 244 } 245 p->radio_data.fm_freq = frequency; 246error: 247 mutex_unlock(&p->lock); 248 return ret; 249} 250 251static int fm_set_freq(struct file *file, void *priv, 252 struct v4l2_frequency *argp) 253{ 254 struct poseidon *p = file->private_data; 255 256 p->file_for_stream = file; 257#ifdef CONFIG_PM 258 p->pm_suspend = pm_fm_suspend; 259 p->pm_resume = pm_fm_resume; 260#endif 261 return set_frequency(p, argp->frequency); 262} 263 264static int tlg_fm_vidioc_g_ctrl(struct file *file, void *priv, 265 struct v4l2_control *arg) 266{ 267 return 0; 268} 269 270static int tlg_fm_vidioc_g_exts_ctrl(struct file *file, void *fh, 271 struct v4l2_ext_controls *ctrls) 272{ 273 struct poseidon *p = file->private_data; 274 int i; 275 276 if (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX) 277 return -EINVAL; 278 279 for (i = 0; i < ctrls->count; i++) { 280 struct v4l2_ext_control *ctrl = ctrls->controls + i; 281 282 if (ctrl->id != V4L2_CID_TUNE_PREEMPHASIS) 283 continue; 284 285 if (i < MAX_PREEMPHASIS) 286 ctrl->value = p->radio_data.pre_emphasis; 287 } 288 return 0; 289} 290 291static int tlg_fm_vidioc_s_exts_ctrl(struct file *file, void *fh, 292 struct v4l2_ext_controls *ctrls) 293{ 294 int i; 295 296 if (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX) 297 return -EINVAL; 298 299 for (i = 0; i < ctrls->count; i++) { 300 struct v4l2_ext_control *ctrl = ctrls->controls + i; 301 302 if (ctrl->id != V4L2_CID_TUNE_PREEMPHASIS) 303 continue; 304 305 if (ctrl->value >= 0 && ctrl->value < MAX_PREEMPHASIS) { 306 struct poseidon *p = file->private_data; 307 int pre_emphasis = preemphasis[ctrl->value]; 308 u32 status; 309 310 send_set_req(p, TUNER_AUD_ANA_STD, 311 pre_emphasis, &status); 312 p->radio_data.pre_emphasis = pre_emphasis; 313 } 314 } 315 return 0; 316} 317 318static int tlg_fm_vidioc_s_ctrl(struct file *file, void *priv, 319 struct v4l2_control *ctrl) 320{ 321 return 0; 322} 323 324static int tlg_fm_vidioc_queryctrl(struct file *file, void *priv, 325 struct v4l2_queryctrl *ctrl) 326{ 327 if (!(ctrl->id & V4L2_CTRL_FLAG_NEXT_CTRL)) 328 return -EINVAL; 329 330 ctrl->id &= ~V4L2_CTRL_FLAG_NEXT_CTRL; 331 if (ctrl->id != V4L2_CID_TUNE_PREEMPHASIS) { 332 /* return the next supported control */ 333 ctrl->id = V4L2_CID_TUNE_PREEMPHASIS; 334 v4l2_ctrl_query_fill(ctrl, V4L2_PREEMPHASIS_DISABLED, 335 V4L2_PREEMPHASIS_75_uS, 1, 336 V4L2_PREEMPHASIS_50_uS); 337 ctrl->flags = V4L2_CTRL_FLAG_UPDATE; 338 return 0; 339 } 340 return -EINVAL; 341} 342 343static int tlg_fm_vidioc_querymenu(struct file *file, void *fh, 344 struct v4l2_querymenu *qmenu) 345{ 346 return v4l2_ctrl_query_menu(qmenu, NULL, NULL); 347} 348 349static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *vt) 350{ 351 return vt->index > 0 ? -EINVAL : 0; 352} 353static int vidioc_s_audio(struct file *file, void *priv, struct v4l2_audio *va) 354{ 355 return (va->index != 0) ? -EINVAL : 0; 356} 357 358static int vidioc_g_audio(struct file *file, void *priv, struct v4l2_audio *a) 359{ 360 a->index = 0; 361 a->mode = 0; 362 a->capability = V4L2_AUDCAP_STEREO; 363 strcpy(a->name, "Radio"); 364 return 0; 365} 366 367static int vidioc_s_input(struct file *filp, void *priv, u32 i) 368{ 369 return (i != 0) ? -EINVAL : 0; 370} 371 372static int vidioc_g_input(struct file *filp, void *priv, u32 *i) 373{ 374 return (*i != 0) ? -EINVAL : 0; 375} 376 377static const struct v4l2_ioctl_ops poseidon_fm_ioctl_ops = { 378 .vidioc_querycap = vidioc_querycap, 379 .vidioc_g_audio = vidioc_g_audio, 380 .vidioc_s_audio = vidioc_s_audio, 381 .vidioc_g_input = vidioc_g_input, 382 .vidioc_s_input = vidioc_s_input, 383 .vidioc_queryctrl = tlg_fm_vidioc_queryctrl, 384 .vidioc_querymenu = tlg_fm_vidioc_querymenu, 385 .vidioc_g_ctrl = tlg_fm_vidioc_g_ctrl, 386 .vidioc_s_ctrl = tlg_fm_vidioc_s_ctrl, 387 .vidioc_s_ext_ctrls = tlg_fm_vidioc_s_exts_ctrl, 388 .vidioc_g_ext_ctrls = tlg_fm_vidioc_g_exts_ctrl, 389 .vidioc_s_tuner = vidioc_s_tuner, 390 .vidioc_g_tuner = tlg_fm_vidioc_g_tuner, 391 .vidioc_g_frequency = fm_get_freq, 392 .vidioc_s_frequency = fm_set_freq, 393}; 394 395static struct video_device poseidon_fm_template = { 396 .name = "Telegent-Radio", 397 .fops = &poseidon_fm_fops, 398 .minor = -1, 399 .release = video_device_release, 400 .ioctl_ops = &poseidon_fm_ioctl_ops, 401}; 402 403int poseidon_fm_init(struct poseidon *p) 404{ 405 struct video_device *fm_dev; 406 407 fm_dev = vdev_init(p, &poseidon_fm_template); 408 if (fm_dev == NULL) 409 return -1; 410 411 if (video_register_device(fm_dev, VFL_TYPE_RADIO, -1) < 0) { 412 video_device_release(fm_dev); 413 return -1; 414 } 415 p->radio_data.fm_dev = fm_dev; 416 return 0; 417} 418 419int poseidon_fm_exit(struct poseidon *p) 420{ 421 destroy_video_device(&p->radio_data.fm_dev); 422 return 0; 423}