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>
8 #include <media/v4l2-dev.h>
10 #include <linux/mutex.h>
11 #include <media/v4l2-ioctl.h>
12 #include <media/v4l2-event.h>
13 #include <media/v4l2-fh.h>
14 #include <linux/sched.h>
16 #include "pd-common.h"
17 #include "vendorcmds.h"
19 static int set_frequency(struct poseidon
*p
, __u32 frequency
);
20 static int poseidon_fm_close(struct file
*filp
);
21 static int poseidon_fm_open(struct file
*filp
);
23 #define TUNER_FREQ_MIN_FM 76000000U
24 #define TUNER_FREQ_MAX_FM 108000000U
26 #define MAX_PREEMPHASIS (V4L2_PREEMPHASIS_75_uS + 1)
27 static int preemphasis
[MAX_PREEMPHASIS
] = {
28 TLG_TUNE_ASTD_NONE
, /* V4L2_PREEMPHASIS_DISABLED */
29 TLG_TUNE_ASTD_FM_EUR
, /* V4L2_PREEMPHASIS_50_uS */
30 TLG_TUNE_ASTD_FM_US
, /* V4L2_PREEMPHASIS_75_uS */
33 static int poseidon_check_mode_radio(struct poseidon
*p
)
38 set_current_state(TASK_INTERRUPTIBLE
);
39 schedule_timeout(HZ
/2);
40 ret
= usb_set_interface(p
->udev
, 0, BULK_ALTERNATE_IFACE
);
44 ret
= set_tuner_mode(p
, TLG_MODE_FM_RADIO
);
48 ret
= send_set_req(p
, SGNL_SRC_SEL
, TLG_SIG_SRC_ANTENNA
, &status
);
49 ret
= send_set_req(p
, TUNER_AUD_ANA_STD
,
50 p
->radio_data
.pre_emphasis
, &status
);
51 ret
|= send_set_req(p
, TUNER_AUD_MODE
,
52 TLG_TUNE_TVAUDIO_MODE_STEREO
, &status
);
53 ret
|= send_set_req(p
, AUDIO_SAMPLE_RATE_SEL
,
54 ATV_AUDIO_RATE_48K
, &status
);
55 ret
|= send_set_req(p
, TUNE_FREQ_SELECT
, TUNER_FREQ_MIN_FM
, &status
);
61 static int pm_fm_suspend(struct poseidon
*p
)
65 usb_set_interface(p
->udev
, 0, 0);
70 static int pm_fm_resume(struct poseidon
*p
)
73 poseidon_check_mode_radio(p
);
74 set_frequency(p
, p
->radio_data
.fm_freq
);
80 static int poseidon_fm_open(struct file
*filp
)
82 struct poseidon
*p
= video_drvdata(filp
);
86 if (p
->state
& POSEIDON_STATE_DISCONNECT
) {
91 if (p
->state
&& !(p
->state
& POSEIDON_STATE_FM
)) {
95 ret
= v4l2_fh_open(filp
);
99 usb_autopm_get_interface(p
->interface
);
101 struct video_device
*vfd
= &p
->radio_data
.fm_dev
;
103 /* default pre-emphasis */
104 if (p
->radio_data
.pre_emphasis
== 0)
105 p
->radio_data
.pre_emphasis
= TLG_TUNE_ASTD_FM_EUR
;
106 set_debug_mode(vfd
, debug_mode
);
108 ret
= poseidon_check_mode_radio(p
);
110 usb_autopm_put_interface(p
->interface
);
113 p
->state
|= POSEIDON_STATE_FM
;
117 mutex_unlock(&p
->lock
);
121 static int poseidon_fm_close(struct file
*filp
)
123 struct poseidon
*p
= video_drvdata(filp
);
124 struct radio_data
*fm
= &p
->radio_data
;
127 mutex_lock(&p
->lock
);
128 if (v4l2_fh_is_singular_file(filp
))
129 p
->state
&= ~POSEIDON_STATE_FM
;
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
);
135 usb_autopm_put_interface(p
->interface
);
136 mutex_unlock(&p
->lock
);
138 kref_put(&p
->kref
, poseidon_delete
);
139 return v4l2_fh_release(filp
);
142 static int vidioc_querycap(struct file
*file
, void *priv
,
143 struct v4l2_capability
*v
)
145 struct poseidon
*p
= video_drvdata(file
);
147 strlcpy(v
->driver
, "tele-radio", sizeof(v
->driver
));
148 strlcpy(v
->card
, "Telegent Poseidon", sizeof(v
->card
));
149 usb_make_path(p
->udev
, v
->bus_info
, sizeof(v
->bus_info
));
150 v
->device_caps
= V4L2_CAP_TUNER
| V4L2_CAP_RADIO
;
151 /* Report all capabilities of the USB device */
152 v
->capabilities
= v
->device_caps
| V4L2_CAP_DEVICE_CAPS
|
153 V4L2_CAP_VIDEO_CAPTURE
| V4L2_CAP_VBI_CAPTURE
|
154 V4L2_CAP_AUDIO
| V4L2_CAP_STREAMING
|
159 static const struct v4l2_file_operations poseidon_fm_fops
= {
160 .owner
= THIS_MODULE
,
161 .open
= poseidon_fm_open
,
162 .release
= poseidon_fm_close
,
163 .poll
= v4l2_ctrl_poll
,
164 .unlocked_ioctl
= video_ioctl2
,
167 static int tlg_fm_vidioc_g_tuner(struct file
*file
, void *priv
,
168 struct v4l2_tuner
*vt
)
170 struct poseidon
*p
= video_drvdata(file
);
171 struct tuner_fm_sig_stat_s fm_stat
= {};
172 int ret
, status
, count
= 5;
177 vt
->type
= V4L2_TUNER_RADIO
;
178 vt
->capability
= V4L2_TUNER_CAP_STEREO
| V4L2_TUNER_CAP_LOW
;
179 vt
->rangelow
= TUNER_FREQ_MIN_FM
* 2 / 125;
180 vt
->rangehigh
= TUNER_FREQ_MAX_FM
* 2 / 125;
181 vt
->rxsubchans
= V4L2_TUNER_SUB_STEREO
;
182 vt
->audmode
= V4L2_TUNER_MODE_STEREO
;
185 strlcpy(vt
->name
, "Radio", sizeof(vt
->name
));
187 mutex_lock(&p
->lock
);
188 ret
= send_get_req(p
, TUNER_STATUS
, TLG_MODE_FM_RADIO
,
189 &fm_stat
, &status
, sizeof(fm_stat
));
191 while (fm_stat
.sig_lock_busy
&& count
-- && !ret
) {
192 set_current_state(TASK_INTERRUPTIBLE
);
193 schedule_timeout(HZ
);
195 ret
= send_get_req(p
, TUNER_STATUS
, TLG_MODE_FM_RADIO
,
196 &fm_stat
, &status
, sizeof(fm_stat
));
198 mutex_unlock(&p
->lock
);
202 } else if ((fm_stat
.sig_present
|| fm_stat
.sig_locked
)
203 && fm_stat
.sig_strength
== 0) {
206 vt
->signal
= (fm_stat
.sig_strength
* 255 / 10) << 8;
211 static int fm_get_freq(struct file
*file
, void *priv
,
212 struct v4l2_frequency
*argp
)
214 struct poseidon
*p
= video_drvdata(file
);
218 argp
->frequency
= p
->radio_data
.fm_freq
;
222 static int set_frequency(struct poseidon
*p
, __u32 frequency
)
227 mutex_lock(&p
->lock
);
229 ret
= send_set_req(p
, TUNER_AUD_ANA_STD
,
230 p
->radio_data
.pre_emphasis
, &status
);
232 freq
= (frequency
* 125) / 2; /* Hz */
233 freq
= clamp(freq
, TUNER_FREQ_MIN_FM
, TUNER_FREQ_MAX_FM
);
235 ret
= send_set_req(p
, TUNE_FREQ_SELECT
, freq
, &status
);
238 ret
= send_set_req(p
, TAKE_REQUEST
, 0, &status
);
240 set_current_state(TASK_INTERRUPTIBLE
);
241 schedule_timeout(HZ
/4);
242 if (!p
->radio_data
.is_radio_streaming
) {
243 ret
= send_set_req(p
, TAKE_REQUEST
, 0, &status
);
244 ret
= send_set_req(p
, PLAY_SERVICE
,
245 TLG_TUNE_PLAY_SVC_START
, &status
);
246 p
->radio_data
.is_radio_streaming
= 1;
248 p
->radio_data
.fm_freq
= freq
* 2 / 125;
250 mutex_unlock(&p
->lock
);
254 static int fm_set_freq(struct file
*file
, void *priv
,
255 const struct v4l2_frequency
*argp
)
257 struct poseidon
*p
= video_drvdata(file
);
261 p
->file_for_stream
= file
;
263 p
->pm_suspend
= pm_fm_suspend
;
264 p
->pm_resume
= pm_fm_resume
;
266 return set_frequency(p
, argp
->frequency
);
269 static int tlg_fm_s_ctrl(struct v4l2_ctrl
*ctrl
)
271 struct poseidon
*p
= container_of(ctrl
->handler
, struct poseidon
,
272 radio_data
.ctrl_handler
);
277 case V4L2_CID_TUNE_PREEMPHASIS
:
278 pre_emphasis
= preemphasis
[ctrl
->val
];
279 send_set_req(p
, TUNER_AUD_ANA_STD
, pre_emphasis
, &status
);
280 p
->radio_data
.pre_emphasis
= pre_emphasis
;
286 static int vidioc_s_tuner(struct file
*file
, void *priv
, const struct v4l2_tuner
*vt
)
288 return vt
->index
> 0 ? -EINVAL
: 0;
291 static const struct v4l2_ctrl_ops tlg_fm_ctrl_ops
= {
292 .s_ctrl
= tlg_fm_s_ctrl
,
295 static const struct v4l2_ioctl_ops poseidon_fm_ioctl_ops
= {
296 .vidioc_querycap
= vidioc_querycap
,
297 .vidioc_s_tuner
= vidioc_s_tuner
,
298 .vidioc_g_tuner
= tlg_fm_vidioc_g_tuner
,
299 .vidioc_g_frequency
= fm_get_freq
,
300 .vidioc_s_frequency
= fm_set_freq
,
301 .vidioc_subscribe_event
= v4l2_ctrl_subscribe_event
,
302 .vidioc_unsubscribe_event
= v4l2_event_unsubscribe
,
305 static struct video_device poseidon_fm_template
= {
306 .name
= "Telegent-Radio",
307 .fops
= &poseidon_fm_fops
,
309 .release
= video_device_release_empty
,
310 .ioctl_ops
= &poseidon_fm_ioctl_ops
,
313 int poseidon_fm_init(struct poseidon
*p
)
315 struct video_device
*vfd
= &p
->radio_data
.fm_dev
;
316 struct v4l2_ctrl_handler
*hdl
= &p
->radio_data
.ctrl_handler
;
318 *vfd
= poseidon_fm_template
;
320 set_frequency(p
, TUNER_FREQ_MIN_FM
);
321 v4l2_ctrl_handler_init(hdl
, 1);
322 v4l2_ctrl_new_std_menu(hdl
, &tlg_fm_ctrl_ops
, V4L2_CID_TUNE_PREEMPHASIS
,
323 V4L2_PREEMPHASIS_75_uS
, 0, V4L2_PREEMPHASIS_50_uS
);
325 v4l2_ctrl_handler_free(hdl
);
328 vfd
->v4l2_dev
= &p
->v4l2_dev
;
329 vfd
->ctrl_handler
= hdl
;
330 set_bit(V4L2_FL_USE_FH_PRIO
, &vfd
->flags
);
331 video_set_drvdata(vfd
, p
);
332 return video_register_device(vfd
, VFL_TYPE_RADIO
, -1);
335 int poseidon_fm_exit(struct poseidon
*p
)
337 video_unregister_device(&p
->radio_data
.fm_dev
);
338 v4l2_ctrl_handler_free(&p
->radio_data
.ctrl_handler
);