diff options
Diffstat (limited to 'drivers/media/video/tlg2300/pd-radio.c')
-rw-r--r-- | drivers/media/video/tlg2300/pd-radio.c | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/drivers/media/video/tlg2300/pd-radio.c b/drivers/media/video/tlg2300/pd-radio.c new file mode 100644 index 00000000000..4fad1dfb92c --- /dev/null +++ b/drivers/media/video/tlg2300/pd-radio.c | |||
@@ -0,0 +1,421 @@ | |||
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 | |||
36 | set_current_state(TASK_INTERRUPTIBLE); | ||
37 | schedule_timeout(HZ/2); | ||
38 | ret = usb_set_interface(p->udev, 0, BULK_ALTERNATE_IFACE); | ||
39 | if (ret < 0) | ||
40 | goto out; | ||
41 | |||
42 | ret = set_tuner_mode(p, TLG_MODE_FM_RADIO); | ||
43 | if (ret != 0) | ||
44 | goto out; | ||
45 | |||
46 | ret = send_set_req(p, SGNL_SRC_SEL, TLG_SIG_SRC_ANTENNA, &status); | ||
47 | ret = send_set_req(p, TUNER_AUD_ANA_STD, | ||
48 | p->radio_data.pre_emphasis, &status); | ||
49 | ret |= send_set_req(p, TUNER_AUD_MODE, | ||
50 | TLG_TUNE_TVAUDIO_MODE_STEREO, &status); | ||
51 | ret |= send_set_req(p, AUDIO_SAMPLE_RATE_SEL, | ||
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); | ||
88 | if (p->state & POSEIDON_STATE_DISCONNECT) { | ||
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; | ||
103 | set_debug_mode(vfd, debug_mode); | ||
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; | ||
124 | uint32_t status; | ||
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; | ||
133 | send_set_req(p, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP, &status); | ||
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)); | ||
151 | v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; | ||
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; | ||
173 | vt->capability = V4L2_TUNER_CAP_STEREO; | ||
174 | vt->rangelow = TUNER_FREQ_MIN_FM / 62500; | ||
175 | vt->rangehigh = TUNER_FREQ_MAX_FM / 62500; | ||
176 | vt->rxsubchans = V4L2_TUNER_SUB_STEREO; | ||
177 | vt->audmode = V4L2_TUNER_MODE_STEREO; | ||
178 | vt->signal = 0; | ||
179 | vt->afc = 0; | ||
180 | |||
181 | mutex_lock(&p->lock); | ||
182 | ret = send_get_req(p, TUNER_STATUS, TLG_MODE_FM_RADIO, | ||
183 | &fm_stat, &status, sizeof(fm_stat)); | ||
184 | |||
185 | while (fm_stat.sig_lock_busy && count-- && !ret) { | ||
186 | set_current_state(TASK_INTERRUPTIBLE); | ||
187 | schedule_timeout(HZ); | ||
188 | |||
189 | ret = send_get_req(p, TUNER_STATUS, TLG_MODE_FM_RADIO, | ||
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 | |||
221 | ret = send_set_req(p, TUNER_AUD_ANA_STD, | ||
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 | |||
235 | set_current_state(TASK_INTERRUPTIBLE); | ||
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 | |||
308 | send_set_req(p, TUNER_AUD_ANA_STD, | ||
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 */ | ||
331 | ctrl->id = V4L2_CID_TUNE_PREEMPHASIS; | ||
332 | v4l2_ctrl_query_fill(ctrl, V4L2_PREEMPHASIS_DISABLED, | ||
333 | V4L2_PREEMPHASIS_75_uS, 1, | ||
334 | V4L2_PREEMPHASIS_50_uS); | ||
335 | ctrl->flags = V4L2_CTRL_FLAG_UPDATE; | ||
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, 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; | ||
360 | a->capability = V4L2_AUDCAP_STEREO; | ||
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, | ||
397 | .release = video_device_release, | ||
398 | .ioctl_ops = &poseidon_fm_ioctl_ops, | ||
399 | }; | ||
400 | |||
401 | int poseidon_fm_init(struct poseidon *p) | ||
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 | |||
417 | int poseidon_fm_exit(struct poseidon *p) | ||
418 | { | ||
419 | destroy_video_device(&p->radio_data.fm_dev); | ||
420 | return 0; | ||
421 | } | ||