diff options
Diffstat (limited to 'drivers/media/video/tlg2300/pd-radio.c')
-rw-r--r-- | drivers/media/video/tlg2300/pd-radio.c | 351 |
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 | |||
18 | static int set_frequency(struct poseidon *p, __u32 frequency); | ||
19 | static int poseidon_fm_close(struct file *filp); | ||
20 | static int poseidon_fm_open(struct file *filp); | ||
21 | |||
22 | #define TUNER_FREQ_MIN_FM 76000000 | ||
23 | #define TUNER_FREQ_MAX_FM 108000000 | ||
24 | |||
25 | static 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); | ||
48 | out: | ||
49 | return ret; | ||
50 | } | ||
51 | |||
52 | #ifdef CONFIG_PM | ||
53 | static 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 | |||
62 | static 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 | |||
72 | static 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; | ||
107 | out: | ||
108 | mutex_unlock(&p->lock); | ||
109 | return ret; | ||
110 | } | ||
111 | |||
112 | static 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 | |||
135 | static 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 | |||
148 | static 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 | |||
155 | int 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 | |||
197 | int 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 | |||
205 | static 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; | ||
236 | error: | ||
237 | mutex_unlock(&p->lock); | ||
238 | return ret; | ||
239 | } | ||
240 | |||
241 | int 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 | |||
253 | int tlg_fm_vidioc_g_ctrl(struct file *file, void *priv, | ||
254 | struct v4l2_control *arg) | ||
255 | { | ||
256 | return 0; | ||
257 | } | ||
258 | |||
259 | int tlg_fm_vidioc_exts_ctrl(struct file *file, void *fh, | ||
260 | struct v4l2_ext_controls *a) | ||
261 | { | ||
262 | return 0; | ||
263 | } | ||
264 | |||
265 | int tlg_fm_vidioc_s_ctrl(struct file *file, void *priv, | ||
266 | struct v4l2_control *arg) | ||
267 | { | ||
268 | return 0; | ||
269 | } | ||
270 | |||
271 | int 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 | |||
279 | static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *vt) | ||
280 | { | ||
281 | return vt->index > 0 ? -EINVAL : 0; | ||
282 | } | ||
283 | static int vidioc_s_audio(struct file *file, void *priv, struct v4l2_audio *va) | ||
284 | { | ||
285 | return (va->index != 0) ? -EINVAL : 0; | ||
286 | } | ||
287 | |||
288 | static 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 | |||
297 | static int vidioc_s_input(struct file *filp, void *priv, u32 i) | ||
298 | { | ||
299 | return (i != 0) ? -EINVAL : 0; | ||
300 | } | ||
301 | |||
302 | static int vidioc_g_input(struct file *filp, void *priv, u32 *i) | ||
303 | { | ||
304 | return (*i != 0) ? -EINVAL : 0; | ||
305 | } | ||
306 | |||
307 | static 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 | |||
323 | static 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 | |||
331 | int 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 | |||
347 | int poseidon_fm_exit(struct poseidon *p) | ||
348 | { | ||
349 | destroy_video_device(&p->radio_data.fm_dev); | ||
350 | return 0; | ||
351 | } | ||