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