aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/video/tlg2300/pd-radio.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/video/tlg2300/pd-radio.c')
-rw-r--r--drivers/media/video/tlg2300/pd-radio.c351
1 files changed, 351 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 000000000000..bdbb0c11b3a9
--- /dev/null
+++ b/drivers/media/video/tlg2300/pd-radio.c
@@ -0,0 +1,351 @@
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
25static int poseidon_check_mode_radio(struct poseidon *p)
26{
27 int ret, radiomode;
28 u32 status;
29
30 set_current_state(TASK_INTERRUPTIBLE);
31 schedule_timeout(HZ/2);
32 ret = usb_set_interface(p->udev, 0, BULK_ALTERNATE_IFACE);
33 if (ret < 0)
34 goto out;
35
36 ret = set_tuner_mode(p, TLG_MODE_FM_RADIO);
37 if (ret != 0)
38 goto out;
39
40 ret = send_set_req(p, SGNL_SRC_SEL, TLG_SIG_SRC_ANTENNA, &status);
41 radiomode = get_audio_std(TLG_MODE_FM_RADIO, p->country_code);
42 ret = send_set_req(p, TUNER_AUD_ANA_STD, radiomode, &status);
43 ret |= send_set_req(p, TUNER_AUD_MODE,
44 TLG_TUNE_TVAUDIO_MODE_STEREO, &status);
45 ret |= send_set_req(p, AUDIO_SAMPLE_RATE_SEL,
46 ATV_AUDIO_RATE_48K, &status);
47 ret |= send_set_req(p, TUNE_FREQ_SELECT, TUNER_FREQ_MIN_FM, &status);
48out:
49 return ret;
50}
51
52#ifdef CONFIG_PM
53static int pm_fm_suspend(struct poseidon *p)
54{
55 logpm(p);
56 pm_alsa_suspend(p);
57 usb_set_interface(p->udev, 0, 0);
58 msleep(300);
59 return 0;
60}
61
62static int pm_fm_resume(struct poseidon *p)
63{
64 logpm(p);
65 poseidon_check_mode_radio(p);
66 set_frequency(p, p->radio_data.fm_freq);
67 pm_alsa_resume(p);
68 return 0;
69}
70#endif
71
72static int poseidon_fm_open(struct file *filp)
73{
74 struct video_device *vfd = video_devdata(filp);
75 struct poseidon *p = video_get_drvdata(vfd);
76 int ret = 0;
77
78 if (!p)
79 return -1;
80
81 mutex_lock(&p->lock);
82 if (p->state & POSEIDON_STATE_DISCONNECT) {
83 ret = -ENODEV;
84 goto out;
85 }
86
87 if (p->state && !(p->state & POSEIDON_STATE_FM)) {
88 ret = -EBUSY;
89 goto out;
90 }
91
92 usb_autopm_get_interface(p->interface);
93 if (0 == p->state) {
94 p->country_code = country_code;
95 set_debug_mode(vfd, debug_mode);
96
97 ret = poseidon_check_mode_radio(p);
98 if (ret < 0) {
99 usb_autopm_put_interface(p->interface);
100 goto out;
101 }
102 p->state |= POSEIDON_STATE_FM;
103 }
104 p->radio_data.users++;
105 kref_get(&p->kref);
106 filp->private_data = p;
107out:
108 mutex_unlock(&p->lock);
109 return ret;
110}
111
112static int poseidon_fm_close(struct file *filp)
113{
114 struct poseidon *p = filp->private_data;
115 struct radio_data *fm = &p->radio_data;
116 uint32_t status;
117
118 mutex_lock(&p->lock);
119 fm->users--;
120 if (0 == fm->users)
121 p->state &= ~POSEIDON_STATE_FM;
122
123 if (fm->is_radio_streaming && filp == p->file_for_stream) {
124 fm->is_radio_streaming = 0;
125 send_set_req(p, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP, &status);
126 }
127 usb_autopm_put_interface(p->interface);
128 mutex_unlock(&p->lock);
129
130 kref_put(&p->kref, poseidon_delete);
131 filp->private_data = NULL;
132 return 0;
133}
134
135static int vidioc_querycap(struct file *file, void *priv,
136 struct v4l2_capability *v)
137{
138 struct poseidon *p = file->private_data;
139
140 strlcpy(v->driver, "tele-radio", sizeof(v->driver));
141 strlcpy(v->card, "Telegent Poseidon", sizeof(v->card));
142 usb_make_path(p->udev, v->bus_info, sizeof(v->bus_info));
143 v->version = KERNEL_VERSION(0, 0, 1);
144 v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
145 return 0;
146}
147
148static const struct v4l2_file_operations poseidon_fm_fops = {
149 .owner = THIS_MODULE,
150 .open = poseidon_fm_open,
151 .release = poseidon_fm_close,
152 .ioctl = video_ioctl2,
153};
154
155int tlg_fm_vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *vt)
156{
157 struct tuner_fm_sig_stat_s fm_stat = {};
158 int ret, status, count = 5;
159 struct poseidon *p = file->private_data;
160
161 if (vt->index != 0)
162 return -EINVAL;
163
164 vt->type = V4L2_TUNER_RADIO;
165 vt->capability = V4L2_TUNER_CAP_STEREO;
166 vt->rangelow = TUNER_FREQ_MIN_FM / 62500;
167 vt->rangehigh = TUNER_FREQ_MAX_FM / 62500;
168 vt->rxsubchans = V4L2_TUNER_SUB_STEREO;
169 vt->audmode = V4L2_TUNER_MODE_STEREO;
170 vt->signal = 0;
171 vt->afc = 0;
172
173 mutex_lock(&p->lock);
174 ret = send_get_req(p, TUNER_STATUS, TLG_MODE_FM_RADIO,
175 &fm_stat, &status, sizeof(fm_stat));
176
177 while (fm_stat.sig_lock_busy && count-- && !ret) {
178 set_current_state(TASK_INTERRUPTIBLE);
179 schedule_timeout(HZ);
180
181 ret = send_get_req(p, TUNER_STATUS, TLG_MODE_FM_RADIO,
182 &fm_stat, &status, sizeof(fm_stat));
183 }
184 mutex_unlock(&p->lock);
185
186 if (ret || status) {
187 vt->signal = 0;
188 } else if ((fm_stat.sig_present || fm_stat.sig_locked)
189 && fm_stat.sig_strength == 0) {
190 vt->signal = 0xffff;
191 } else
192 vt->signal = (fm_stat.sig_strength * 255 / 10) << 8;
193
194 return 0;
195}
196
197int fm_get_freq(struct file *file, void *priv, struct v4l2_frequency *argp)
198{
199 struct poseidon *p = file->private_data;
200
201 argp->frequency = p->radio_data.fm_freq;
202 return 0;
203}
204
205static int set_frequency(struct poseidon *p, __u32 frequency)
206{
207 __u32 freq ;
208 int ret, status, radiomode;
209
210 mutex_lock(&p->lock);
211
212 radiomode = get_audio_std(TLG_MODE_FM_RADIO, p->country_code);
213 /*NTSC 8,PAL 2 */
214 ret = send_set_req(p, TUNER_AUD_ANA_STD, radiomode, &status);
215
216 freq = (frequency * 125) * 500 / 1000;/* kHZ */
217 if (freq < TUNER_FREQ_MIN_FM/1000 || freq > TUNER_FREQ_MAX_FM/1000) {
218 ret = -EINVAL;
219 goto error;
220 }
221
222 ret = send_set_req(p, TUNE_FREQ_SELECT, freq, &status);
223 if (ret < 0)
224 goto error ;
225 ret = send_set_req(p, TAKE_REQUEST, 0, &status);
226
227 set_current_state(TASK_INTERRUPTIBLE);
228 schedule_timeout(HZ/4);
229 if (!p->radio_data.is_radio_streaming) {
230 ret = send_set_req(p, TAKE_REQUEST, 0, &status);
231 ret = send_set_req(p, PLAY_SERVICE,
232 TLG_TUNE_PLAY_SVC_START, &status);
233 p->radio_data.is_radio_streaming = 1;
234 }
235 p->radio_data.fm_freq = frequency;
236error:
237 mutex_unlock(&p->lock);
238 return ret;
239}
240
241int fm_set_freq(struct file *file, void *priv, struct v4l2_frequency *argp)
242{
243 struct poseidon *p = file->private_data;
244
245 p->file_for_stream = file;
246#ifdef CONFIG_PM
247 p->pm_suspend = pm_fm_suspend;
248 p->pm_resume = pm_fm_resume;
249#endif
250 return set_frequency(p, argp->frequency);
251}
252
253int tlg_fm_vidioc_g_ctrl(struct file *file, void *priv,
254 struct v4l2_control *arg)
255{
256 return 0;
257}
258
259int tlg_fm_vidioc_exts_ctrl(struct file *file, void *fh,
260 struct v4l2_ext_controls *a)
261{
262 return 0;
263}
264
265int tlg_fm_vidioc_s_ctrl(struct file *file, void *priv,
266 struct v4l2_control *arg)
267{
268 return 0;
269}
270
271int tlg_fm_vidioc_queryctrl(struct file *file, void *priv,
272 struct v4l2_queryctrl *arg)
273{
274 arg->minimum = 0;
275 arg->maximum = 65535;
276 return 0;
277}
278
279static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *vt)
280{
281 return vt->index > 0 ? -EINVAL : 0;
282}
283static int vidioc_s_audio(struct file *file, void *priv, struct v4l2_audio *va)
284{
285 return (va->index != 0) ? -EINVAL : 0;
286}
287
288static int vidioc_g_audio(struct file *file, void *priv, struct v4l2_audio *a)
289{
290 a->index = 0;
291 a->mode = 0;
292 a->capability = V4L2_AUDCAP_STEREO;
293 strcpy(a->name, "Radio");
294 return 0;
295}
296
297static int vidioc_s_input(struct file *filp, void *priv, u32 i)
298{
299 return (i != 0) ? -EINVAL : 0;
300}
301
302static int vidioc_g_input(struct file *filp, void *priv, u32 *i)
303{
304 return (*i != 0) ? -EINVAL : 0;
305}
306
307static const struct v4l2_ioctl_ops poseidon_fm_ioctl_ops = {
308 .vidioc_querycap = vidioc_querycap,
309 .vidioc_g_audio = vidioc_g_audio,
310 .vidioc_s_audio = vidioc_s_audio,
311 .vidioc_g_input = vidioc_g_input,
312 .vidioc_s_input = vidioc_s_input,
313 .vidioc_queryctrl = tlg_fm_vidioc_queryctrl,
314 .vidioc_g_ctrl = tlg_fm_vidioc_g_ctrl,
315 .vidioc_s_ctrl = tlg_fm_vidioc_s_ctrl,
316 .vidioc_s_ext_ctrls = tlg_fm_vidioc_exts_ctrl,
317 .vidioc_s_tuner = vidioc_s_tuner,
318 .vidioc_g_tuner = tlg_fm_vidioc_g_tuner,
319 .vidioc_g_frequency = fm_get_freq,
320 .vidioc_s_frequency = fm_set_freq,
321};
322
323static struct video_device poseidon_fm_template = {
324 .name = "Telegent-Radio",
325 .fops = &poseidon_fm_fops,
326 .minor = -1,
327 .release = video_device_release,
328 .ioctl_ops = &poseidon_fm_ioctl_ops,
329};
330
331int poseidon_fm_init(struct poseidon *p)
332{
333 struct video_device *fm_dev;
334
335 fm_dev = vdev_init(p, &poseidon_fm_template);
336 if (fm_dev == NULL)
337 return -1;
338
339 if (video_register_device(fm_dev, VFL_TYPE_RADIO, -1) < 0) {
340 video_device_release(fm_dev);
341 return -1;
342 }
343 p->radio_data.fm_dev = fm_dev;
344 return 0;
345}
346
347int poseidon_fm_exit(struct poseidon *p)
348{
349 destroy_video_device(&p->radio_data.fm_dev);
350 return 0;
351}