V4L/DVB: Add driver for Telegent tlg2300
pd-common.h contains the common data structures, while vendorcmds.h contains the vendor commands for firmware. [mchehab@redhat.com: Folded the 10 patches with the driver] Signed-off-by: Huang Shijie <shijie8@gmail.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
This commit is contained in:
parent
433763faec
commit
5b3f03f044
|
@ -0,0 +1,231 @@
|
|||
tlg2300 release notes
|
||||
====================
|
||||
|
||||
This is a v4l2/dvb device driver for the tlg2300 chip.
|
||||
|
||||
|
||||
current status
|
||||
==============
|
||||
|
||||
video
|
||||
- support mmap and read().(no overlay)
|
||||
|
||||
audio
|
||||
- The driver will register a ALSA card for the audio input.
|
||||
|
||||
vbi
|
||||
- Works for almost TV norms.
|
||||
|
||||
dvb-t
|
||||
- works for DVB-T
|
||||
|
||||
FM
|
||||
- Works for radio.
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
TESTED APPLICATIONS:
|
||||
|
||||
-VLC1.0.4 test the video and dvb. The GUI is friendly to use.
|
||||
|
||||
-Mplayer test the video.
|
||||
|
||||
-Mplayer test the FM. The mplayer should be compiled with --enable-radio and
|
||||
--enable-radio-capture.
|
||||
The command runs as this(The alsa audio registers to card 1):
|
||||
#mplayer radio://103.7/capture/ -radio adevice=hw=1,0:arate=48000 \
|
||||
-rawaudio rate=48000:channels=2
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
KNOWN PROBLEMS:
|
||||
|
||||
country code
|
||||
- The firmware of the chip needs the country code to determine
|
||||
the stardards of video and audio when it runs for analog TV or radio.
|
||||
The DVB-T does not need the country code.
|
||||
|
||||
So you must set the country-code correctly. The V4L2 does not have
|
||||
the interface,the driver has to provide a parameter `country_code'.
|
||||
|
||||
You could set the coutry code in two ways, take USA as example
|
||||
(The USA's country code is 1):
|
||||
|
||||
[1] add the following line in /etc/modprobe.conf before you insert the
|
||||
card into USB hub's port :
|
||||
poseidon country_code=1
|
||||
|
||||
[2] You can also modify the parameter at runtime (before you run the
|
||||
application such as VLC)
|
||||
#echo 1 > /sys/module/poseidon/parameter/country_code
|
||||
|
||||
The known country codes show below:
|
||||
country code : country
|
||||
93 "Afghanistan"
|
||||
355 "Albania"
|
||||
213 "Algeria"
|
||||
684 "American Samoa"
|
||||
376 "Andorra"
|
||||
244 "Angola"
|
||||
54 "Argentina"
|
||||
374 "Armenia"
|
||||
61 "Australia"
|
||||
43 "Austria"
|
||||
994 "Azerbaijan"
|
||||
973 "Bahrain"
|
||||
880 "Bangladesh"
|
||||
375 "Belarus"
|
||||
32 "Belgium"
|
||||
501 "Belize"
|
||||
229 "Benin"
|
||||
591 "Bolivia"
|
||||
387 "Bosnia and Herzegovina"
|
||||
267 "Botswana"
|
||||
55 "Brazil"
|
||||
673 "Brunei Darussalam"
|
||||
359 "Bulgalia"
|
||||
226 "Burkina Faso"
|
||||
257 "Burundi"
|
||||
237 "Cameroon"
|
||||
1 "Canada"
|
||||
236 "Central African Republic"
|
||||
235 "Chad"
|
||||
56 "Chile"
|
||||
86 "China"
|
||||
57 "Colombia"
|
||||
242 "Congo"
|
||||
243 "Congo, Dem. Rep. of "
|
||||
506 "Costa Rica"
|
||||
385 "Croatia"
|
||||
53 "Cuba or Guantanamo Bay"
|
||||
357 "Cyprus"
|
||||
420 "Czech Republic"
|
||||
45 "Denmark"
|
||||
246 "Diego Garcia"
|
||||
253 "Djibouti"
|
||||
593 "Ecuador"
|
||||
20 "Egypt"
|
||||
503 "El Salvador"
|
||||
240 "Equatorial Guinea"
|
||||
372 "Estonia"
|
||||
251 "Ethiopia"
|
||||
358 "Finland"
|
||||
33 "France"
|
||||
594 "French Guiana"
|
||||
689 "French Polynesia"
|
||||
241 "Gabonese Republic"
|
||||
220 "Gambia"
|
||||
995 "Georgia"
|
||||
49 "Germany"
|
||||
233 "Ghana"
|
||||
350 "Gibraltar"
|
||||
30 "Greece"
|
||||
299 "Greenland"
|
||||
671 "Guam"
|
||||
502 "Guatemala"
|
||||
592 "Guyana"
|
||||
509 "Haiti"
|
||||
504 "Honduras"
|
||||
852 "Hong Kong SAR, China"
|
||||
36 "Hungary"
|
||||
354 "Iceland"
|
||||
91 "India"
|
||||
98 "Iran"
|
||||
964 "Iraq"
|
||||
353 "Ireland"
|
||||
972 "Israel"
|
||||
39 "Italy or Vatican City"
|
||||
225 "Ivory Coast"
|
||||
81 "Japan"
|
||||
962 "Jordan"
|
||||
7 "Kazakhstan or Kyrgyzstan"
|
||||
254 "Kenya"
|
||||
686 "Kiribati"
|
||||
965 "Kuwait"
|
||||
856 "Laos"
|
||||
371 "Latvia"
|
||||
961 "Lebanon"
|
||||
266 "Lesotho"
|
||||
231 "Liberia"
|
||||
218 "Libya"
|
||||
41 "Liechtenstein or Switzerland"
|
||||
370 "Lithuania"
|
||||
352 "Luxembourg"
|
||||
853 "Macau SAR, China"
|
||||
261 "Madagascar"
|
||||
60 "Malaysia"
|
||||
960 "Maldives"
|
||||
223 "Mali Republic"
|
||||
356 "Malta"
|
||||
692 "Marshall Islands"
|
||||
596 "Martinique"
|
||||
222 "Mauritania"
|
||||
230 "Mauritus"
|
||||
52 "Mexico"
|
||||
691 "Micronesia"
|
||||
373 "Moldova"
|
||||
377 "Monaco"
|
||||
976 "Mongolia"
|
||||
212 "Morocco"
|
||||
258 "Mozambique"
|
||||
95 "Myanmar"
|
||||
264 "Namibia"
|
||||
674 "Nauru"
|
||||
31 "Netherlands"
|
||||
687 "New Caledonia"
|
||||
64 "New Zealand"
|
||||
505 "Nicaragua"
|
||||
227 "Niger"
|
||||
234 "Nigeria"
|
||||
850 "North Korea"
|
||||
47 "Norway"
|
||||
968 "Oman"
|
||||
92 "Pakistan"
|
||||
680 "Palau"
|
||||
507 "Panama"
|
||||
675 "Papua New Guinea"
|
||||
595 "Paraguay"
|
||||
51 "Peru"
|
||||
63 "Philippines"
|
||||
48 "Poland"
|
||||
351 "Portugal"
|
||||
974 "Qatar"
|
||||
262 "Reunion Island"
|
||||
40 "Romania"
|
||||
7 "Russia"
|
||||
378 "San Marino"
|
||||
239 "Sao Tome and Principe"
|
||||
966 "Saudi Arabia"
|
||||
221 "Senegal"
|
||||
248 "Seychelles Republic"
|
||||
232 "Sierra Leone"
|
||||
65 "Singapore"
|
||||
421 "Slovak Republic"
|
||||
386 "Slovenia"
|
||||
27 "South Africa"
|
||||
82 "South Korea "
|
||||
34 "Spain"
|
||||
94 "Sri Lanka"
|
||||
508 "St. Pierre and Miquelon"
|
||||
249 "Sudan"
|
||||
597 "Suriname"
|
||||
268 "Swaziland"
|
||||
46 "Sweden"
|
||||
963 "Syria"
|
||||
886 "Taiwan Region"
|
||||
255 "Tanzania"
|
||||
66 "Thailand"
|
||||
228 "Togolese Republic"
|
||||
216 "Tunisia"
|
||||
90 "Turkey"
|
||||
993 "Turkmenistan"
|
||||
256 "Uganda"
|
||||
380 "Ukraine"
|
||||
971 "United Arab Emirates"
|
||||
44 "United Kingdom"
|
||||
1 "United States of America"
|
||||
598 "Uruguay"
|
||||
58 "Venezuela"
|
||||
84 "Vietnam"
|
||||
967 "Yemen"
|
||||
260 "Zambia"
|
||||
255 "Zanzibar"
|
||||
263 "Zimbabwe"
|
|
@ -4676,6 +4676,14 @@ F: drivers/media/common/saa7146*
|
|||
F: drivers/media/video/*7146*
|
||||
F: include/media/*7146*
|
||||
|
||||
TLG2300 VIDEO4LINUX-2 DRIVER
|
||||
M Huang Shijie <shijie8@gmail.com>
|
||||
M Kang Yong <kangyong@telegent.com>
|
||||
M Zhang Xiaobing <xbzhang@telegent.com>
|
||||
S: Supported
|
||||
F: drivers/media/video/tlg2300
|
||||
|
||||
|
||||
SC1200 WDT DRIVER
|
||||
M: Zwane Mwaikambo <zwane@arm.linux.org.uk>
|
||||
S: Maintained
|
||||
|
|
|
@ -949,6 +949,8 @@ source "drivers/media/video/hdpvr/Kconfig"
|
|||
|
||||
source "drivers/media/video/em28xx/Kconfig"
|
||||
|
||||
source "drivers/media/video/tlg2300/Kconfig"
|
||||
|
||||
source "drivers/media/video/cx231xx/Kconfig"
|
||||
|
||||
source "drivers/media/video/usbvision/Kconfig"
|
||||
|
|
|
@ -99,6 +99,7 @@ obj-$(CONFIG_VIDEO_MEYE) += meye.o
|
|||
obj-$(CONFIG_VIDEO_SAA7134) += saa7134/
|
||||
obj-$(CONFIG_VIDEO_CX88) += cx88/
|
||||
obj-$(CONFIG_VIDEO_EM28XX) += em28xx/
|
||||
obj-$(CONFIG_VIDEO_TLG2300) += tlg2300/
|
||||
obj-$(CONFIG_VIDEO_CX231XX) += cx231xx/
|
||||
obj-$(CONFIG_VIDEO_USBVISION) += usbvision/
|
||||
obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
config VIDEO_TLG2300
|
||||
tristate "Telegent TLG2300 USB video capture support"
|
||||
depends on VIDEO_DEV && I2C && INPUT && SND && DVB_CORE
|
||||
select VIDEO_TUNER
|
||||
select VIDEO_TVEEPROM
|
||||
select VIDEO_IR
|
||||
select VIDEOBUF_VMALLOC
|
||||
select SND_PCM
|
||||
select VIDEOBUF_DVB
|
||||
|
||||
---help---
|
||||
This is a video4linux driver for Telegent tlg2300 based TV cards.
|
||||
The driver supports V4L2, DVB-T and radio.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called poseidon
|
|
@ -0,0 +1,9 @@
|
|||
poseidon-objs := pd-video.o pd-alsa.o pd-dvb.o pd-radio.o pd-main.o
|
||||
|
||||
obj-$(CONFIG_VIDEO_TLG2300) += poseidon.o
|
||||
|
||||
EXTRA_CFLAGS += -Idrivers/media/video
|
||||
EXTRA_CFLAGS += -Idrivers/media/common/tuners
|
||||
EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core
|
||||
EXTRA_CFLAGS += -Idrivers/media/dvb/frontends
|
||||
|
|
@ -0,0 +1,332 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/sound.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/soundcard.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/info.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/control.h>
|
||||
#include <media/v4l2-common.h>
|
||||
#include "pd-common.h"
|
||||
#include "vendorcmds.h"
|
||||
|
||||
static void complete_handler_audio(struct urb *urb);
|
||||
#define AUDIO_EP (0x83)
|
||||
#define AUDIO_BUF_SIZE (512)
|
||||
#define PERIOD_SIZE (1024 * 8)
|
||||
#define PERIOD_MIN (4)
|
||||
#define PERIOD_MAX PERIOD_MIN
|
||||
|
||||
static struct snd_pcm_hardware snd_pd_hw_capture = {
|
||||
.info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_MMAP_VALID,
|
||||
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
|
||||
.rate_min = 48000,
|
||||
.rate_max = 48000,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.buffer_bytes_max = PERIOD_SIZE * PERIOD_MIN,
|
||||
.period_bytes_min = PERIOD_SIZE,
|
||||
.period_bytes_max = PERIOD_SIZE,
|
||||
.periods_min = PERIOD_MIN,
|
||||
.periods_max = PERIOD_MAX,
|
||||
/*
|
||||
.buffer_bytes_max = 62720 * 8,
|
||||
.period_bytes_min = 64,
|
||||
.period_bytes_max = 12544,
|
||||
.periods_min = 2,
|
||||
.periods_max = 98
|
||||
*/
|
||||
};
|
||||
|
||||
static int snd_pd_capture_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct poseidon *p = snd_pcm_substream_chip(substream);
|
||||
struct poseidon_audio *pa = &p->audio;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
if (!p)
|
||||
return -ENODEV;
|
||||
pa->users++;
|
||||
pa->card_close = 0;
|
||||
pa->capture_pcm_substream = substream;
|
||||
runtime->private_data = p;
|
||||
|
||||
runtime->hw = snd_pd_hw_capture;
|
||||
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
usb_autopm_get_interface(p->interface);
|
||||
kref_get(&p->kref);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_pd_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct poseidon *p = snd_pcm_substream_chip(substream);
|
||||
struct poseidon_audio *pa = &p->audio;
|
||||
|
||||
pa->users--;
|
||||
pa->card_close = 1;
|
||||
usb_autopm_put_interface(p->interface);
|
||||
kref_put(&p->kref, poseidon_delete);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_pd_hw_capture_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
unsigned int size;
|
||||
|
||||
size = params_buffer_bytes(hw_params);
|
||||
if (runtime->dma_area) {
|
||||
if (runtime->dma_bytes > size)
|
||||
return 0;
|
||||
vfree(runtime->dma_area);
|
||||
}
|
||||
runtime->dma_area = vmalloc(size);
|
||||
if (!runtime->dma_area)
|
||||
return -ENOMEM;
|
||||
else
|
||||
runtime->dma_bytes = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int audio_buf_free(struct poseidon *p)
|
||||
{
|
||||
struct poseidon_audio *pa = &p->audio;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < AUDIO_BUFS; i++)
|
||||
if (pa->urb_array[i])
|
||||
usb_kill_urb(pa->urb_array[i]);
|
||||
free_all_urb_generic(pa->urb_array, AUDIO_BUFS);
|
||||
logpm();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_pd_hw_capture_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct poseidon *p = snd_pcm_substream_chip(substream);
|
||||
|
||||
logpm();
|
||||
audio_buf_free(p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_pd_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define AUDIO_TRAILER_SIZE (16)
|
||||
static inline void handle_audio_data(struct urb *urb, int *period_elapsed)
|
||||
{
|
||||
struct poseidon_audio *pa = urb->context;
|
||||
struct snd_pcm_runtime *runtime = pa->capture_pcm_substream->runtime;
|
||||
|
||||
int stride = runtime->frame_bits >> 3;
|
||||
int len = urb->actual_length / stride;
|
||||
unsigned char *cp = urb->transfer_buffer;
|
||||
unsigned int oldptr = pa->rcv_position;
|
||||
|
||||
if (urb->actual_length == AUDIO_BUF_SIZE - 4)
|
||||
len -= (AUDIO_TRAILER_SIZE / stride);
|
||||
|
||||
/* do the copy */
|
||||
if (oldptr + len >= runtime->buffer_size) {
|
||||
unsigned int cnt = runtime->buffer_size - oldptr;
|
||||
|
||||
memcpy(runtime->dma_area + oldptr * stride, cp, cnt * stride);
|
||||
memcpy(runtime->dma_area, (cp + cnt * stride),
|
||||
(len * stride - cnt * stride));
|
||||
} else
|
||||
memcpy(runtime->dma_area + oldptr * stride, cp, len * stride);
|
||||
|
||||
/* update the statas */
|
||||
snd_pcm_stream_lock(pa->capture_pcm_substream);
|
||||
pa->rcv_position += len;
|
||||
if (pa->rcv_position >= runtime->buffer_size)
|
||||
pa->rcv_position -= runtime->buffer_size;
|
||||
|
||||
pa->copied_position += (len);
|
||||
if (pa->copied_position >= runtime->period_size) {
|
||||
pa->copied_position -= runtime->period_size;
|
||||
*period_elapsed = 1;
|
||||
}
|
||||
snd_pcm_stream_unlock(pa->capture_pcm_substream);
|
||||
}
|
||||
|
||||
static void complete_handler_audio(struct urb *urb)
|
||||
{
|
||||
struct poseidon_audio *pa = urb->context;
|
||||
struct snd_pcm_substream *substream = pa->capture_pcm_substream;
|
||||
int period_elapsed = 0;
|
||||
int ret;
|
||||
|
||||
if (1 == pa->card_close || pa->capture_stream != STREAM_ON)
|
||||
return;
|
||||
|
||||
if (urb->status != 0) {
|
||||
/*if (urb->status == -ESHUTDOWN)*/
|
||||
return;
|
||||
}
|
||||
|
||||
if (substream) {
|
||||
if (urb->actual_length) {
|
||||
handle_audio_data(urb, &period_elapsed);
|
||||
if (period_elapsed)
|
||||
snd_pcm_period_elapsed(substream);
|
||||
}
|
||||
}
|
||||
|
||||
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (ret < 0)
|
||||
log("audio urb failed (errcod = %i)", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
static int fire_audio_urb(struct poseidon *p)
|
||||
{
|
||||
int i, ret = 0;
|
||||
struct poseidon_audio *pa = &p->audio;
|
||||
|
||||
alloc_bulk_urbs_generic(pa->urb_array, AUDIO_BUFS,
|
||||
p->udev, AUDIO_EP,
|
||||
AUDIO_BUF_SIZE, GFP_ATOMIC,
|
||||
complete_handler_audio, pa);
|
||||
|
||||
for (i = 0; i < AUDIO_BUFS; i++) {
|
||||
ret = usb_submit_urb(pa->urb_array[i], GFP_KERNEL);
|
||||
if (ret)
|
||||
log("urb err : %d", ret);
|
||||
}
|
||||
log();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snd_pd_capture_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct poseidon *p = snd_pcm_substream_chip(substream);
|
||||
struct poseidon_audio *pa = &p->audio;
|
||||
|
||||
if (debug_mode)
|
||||
log("cmd %d, audio stat : %d\n", cmd, pa->capture_stream);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
if (pa->capture_stream == STREAM_ON)
|
||||
return 0;
|
||||
|
||||
pa->rcv_position = pa->copied_position = 0;
|
||||
pa->capture_stream = STREAM_ON;
|
||||
|
||||
if (in_hibernation(p))
|
||||
return 0;
|
||||
fire_audio_urb(p);
|
||||
return 0;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
pa->capture_stream = STREAM_SUSPEND;
|
||||
return 0;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
pa->capture_stream = STREAM_OFF;
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t
|
||||
snd_pd_capture_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct poseidon *p = snd_pcm_substream_chip(substream);
|
||||
struct poseidon_audio *pa = &p->audio;
|
||||
return pa->rcv_position;
|
||||
}
|
||||
|
||||
static struct page *snd_pcm_pd_get_page(struct snd_pcm_substream *subs,
|
||||
unsigned long offset)
|
||||
{
|
||||
void *pageptr = subs->runtime->dma_area + offset;
|
||||
return vmalloc_to_page(pageptr);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops pcm_capture_ops = {
|
||||
.open = snd_pd_capture_open,
|
||||
.close = snd_pd_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_pd_hw_capture_params,
|
||||
.hw_free = snd_pd_hw_capture_free,
|
||||
.prepare = snd_pd_prepare,
|
||||
.trigger = snd_pd_capture_trigger,
|
||||
.pointer = snd_pd_capture_pointer,
|
||||
.page = snd_pcm_pd_get_page,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
int pm_alsa_suspend(struct poseidon *p)
|
||||
{
|
||||
logpm(p);
|
||||
audio_buf_free(p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pm_alsa_resume(struct poseidon *p)
|
||||
{
|
||||
logpm(p);
|
||||
fire_audio_urb(p);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int poseidon_audio_init(struct poseidon *p)
|
||||
{
|
||||
struct poseidon_audio *pa = &p->audio;
|
||||
struct snd_card *card;
|
||||
struct snd_pcm *pcm;
|
||||
int ret;
|
||||
|
||||
ret = snd_card_create(-1, "Telegent", THIS_MODULE, 0, &card);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
ret = snd_pcm_new(card, "poseidon audio", 0, 0, 1, &pcm);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops);
|
||||
pcm->info_flags = 0;
|
||||
pcm->private_data = p;
|
||||
strcpy(pcm->name, "poseidon audio capture");
|
||||
|
||||
strcpy(card->driver, "ALSA driver");
|
||||
strcpy(card->shortname, "poseidon Audio");
|
||||
strcpy(card->longname, "poseidon ALSA Audio");
|
||||
|
||||
if (snd_card_register(card)) {
|
||||
snd_card_free(card);
|
||||
return -ENOMEM;
|
||||
}
|
||||
pa->card = card;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int poseidon_audio_free(struct poseidon *p)
|
||||
{
|
||||
struct poseidon_audio *pa = &p->audio;
|
||||
|
||||
if (pa->card)
|
||||
snd_card_free(pa->card);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,280 @@
|
|||
#ifndef PD_COMMON_H
|
||||
#define PD_COMMON_H
|
||||
|
||||
#include <linux/version.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/poll.h>
|
||||
#include <media/videobuf-vmalloc.h>
|
||||
#include <media/v4l2-device.h>
|
||||
|
||||
#include "dvb_frontend.h"
|
||||
#include "dvbdev.h"
|
||||
#include "dvb_demux.h"
|
||||
#include "dmxdev.h"
|
||||
|
||||
#define SBUF_NUM 8
|
||||
#define MAX_BUFFER_NUM 6
|
||||
#define PK_PER_URB 32
|
||||
#define ISO_PKT_SIZE 3072
|
||||
|
||||
#define POSEIDON_STATE_NONE (0x0000)
|
||||
#define POSEIDON_STATE_ANALOG (0x0001)
|
||||
#define POSEIDON_STATE_FM (0x0002)
|
||||
#define POSEIDON_STATE_DVBT (0x0004)
|
||||
#define POSEIDON_STATE_VBI (0x0008)
|
||||
#define POSEIDON_STATE_DISCONNECT (0x0080)
|
||||
|
||||
#define PM_SUSPEND_DELAY 3
|
||||
|
||||
#define V4L_PAL_VBI_LINES 18
|
||||
#define V4L_NTSC_VBI_LINES 12
|
||||
#define V4L_PAL_VBI_FRAMESIZE (V4L_PAL_VBI_LINES * 1440 * 2)
|
||||
#define V4L_NTSC_VBI_FRAMESIZE (V4L_NTSC_VBI_LINES * 1440 * 2)
|
||||
|
||||
#define TUNER_FREQ_MIN (45000000)
|
||||
#define TUNER_FREQ_MAX (862000000)
|
||||
|
||||
struct vbi_data {
|
||||
struct video_device *v_dev;
|
||||
struct video_data *video;
|
||||
struct front_face *front;
|
||||
|
||||
unsigned int copied;
|
||||
unsigned int vbi_size; /* the whole size of two fields */
|
||||
int users;
|
||||
};
|
||||
|
||||
/*
|
||||
* This is the running context of the video, it is useful for
|
||||
* resume()
|
||||
*/
|
||||
struct running_context {
|
||||
u32 freq; /* VIDIOC_S_FREQUENCY */
|
||||
int audio_idx; /* VIDIOC_S_TUNER */
|
||||
v4l2_std_id tvnormid; /* VIDIOC_S_STD */
|
||||
int sig_index; /* VIDIOC_S_INPUT */
|
||||
struct v4l2_pix_format pix; /* VIDIOC_S_FMT */
|
||||
};
|
||||
|
||||
struct video_data {
|
||||
/* v4l2 video device */
|
||||
struct video_device *v_dev;
|
||||
|
||||
/* the working context */
|
||||
struct running_context context;
|
||||
|
||||
/* for data copy */
|
||||
int field_count;
|
||||
|
||||
char *dst;
|
||||
int lines_copied;
|
||||
int prev_left;
|
||||
|
||||
int lines_per_field;
|
||||
int lines_size;
|
||||
|
||||
/* for communication */
|
||||
u8 endpoint_addr;
|
||||
struct urb *urb_array[SBUF_NUM];
|
||||
struct vbi_data *vbi;
|
||||
struct poseidon *pd;
|
||||
struct front_face *front;
|
||||
|
||||
int is_streaming;
|
||||
int users;
|
||||
|
||||
/* for bubble handler */
|
||||
struct work_struct bubble_work;
|
||||
};
|
||||
|
||||
enum pcm_stream_state {
|
||||
STREAM_OFF,
|
||||
STREAM_ON,
|
||||
STREAM_SUSPEND,
|
||||
};
|
||||
|
||||
#define AUDIO_BUFS (3)
|
||||
#define CAPTURE_STREAM_EN 1
|
||||
struct poseidon_audio {
|
||||
struct urb *urb_array[AUDIO_BUFS];
|
||||
unsigned int copied_position;
|
||||
struct snd_pcm_substream *capture_pcm_substream;
|
||||
|
||||
unsigned int rcv_position;
|
||||
struct snd_card *card;
|
||||
int card_close;
|
||||
|
||||
int users;
|
||||
int pm_state;
|
||||
enum pcm_stream_state capture_stream;
|
||||
};
|
||||
|
||||
struct radio_data {
|
||||
__u32 fm_freq;
|
||||
int users;
|
||||
unsigned int is_radio_streaming;
|
||||
struct video_device *fm_dev;
|
||||
};
|
||||
|
||||
#define DVB_SBUF_NUM 4
|
||||
#define DVB_URB_BUF_SIZE 0x2000
|
||||
struct pd_dvb_adapter {
|
||||
struct dvb_adapter dvb_adap;
|
||||
struct dvb_frontend dvb_fe;
|
||||
struct dmxdev dmxdev;
|
||||
struct dvb_demux demux;
|
||||
|
||||
atomic_t users;
|
||||
atomic_t active_feed;
|
||||
|
||||
/* data transfer */
|
||||
s32 is_streaming;
|
||||
struct urb *urb_array[DVB_SBUF_NUM];
|
||||
struct poseidon *pd_device;
|
||||
u8 ep_addr;
|
||||
u8 reserved[3];
|
||||
|
||||
/* data for power resume*/
|
||||
struct dvb_frontend_parameters fe_param;
|
||||
|
||||
/* for channel scanning */
|
||||
int prev_freq;
|
||||
int bandwidth;
|
||||
unsigned long last_jiffies;
|
||||
};
|
||||
|
||||
struct front_face {
|
||||
/* use this field to distinguish VIDEO and VBI */
|
||||
enum v4l2_buf_type type;
|
||||
|
||||
/* for host */
|
||||
struct videobuf_queue q;
|
||||
|
||||
/* the bridge for host and device */
|
||||
struct videobuf_buffer *curr_frame;
|
||||
|
||||
/* for device */
|
||||
spinlock_t queue_lock;
|
||||
struct list_head active;
|
||||
struct poseidon *pd;
|
||||
};
|
||||
|
||||
struct poseidon {
|
||||
struct list_head device_list;
|
||||
|
||||
struct mutex lock;
|
||||
struct kref kref;
|
||||
|
||||
/* for V4L2 */
|
||||
struct v4l2_device v4l2_dev;
|
||||
|
||||
/* hardware info */
|
||||
struct usb_device *udev;
|
||||
struct usb_interface *interface;
|
||||
int cur_transfer_mode;
|
||||
|
||||
struct video_data video_data; /* video */
|
||||
struct vbi_data vbi_data; /* vbi */
|
||||
struct poseidon_audio audio; /* audio (alsa) */
|
||||
struct radio_data radio_data; /* FM */
|
||||
struct pd_dvb_adapter dvb_data; /* DVB */
|
||||
|
||||
u32 state;
|
||||
int country_code;
|
||||
struct file *file_for_stream; /* the active stream*/
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
int (*pm_suspend)(struct poseidon *);
|
||||
int (*pm_resume)(struct poseidon *);
|
||||
pm_message_t msg;
|
||||
|
||||
struct work_struct pm_work;
|
||||
u8 portnum;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct poseidon_format {
|
||||
char *name;
|
||||
int fourcc; /* video4linux 2 */
|
||||
int depth; /* bit/pixel */
|
||||
int flags;
|
||||
};
|
||||
|
||||
struct poseidon_tvnorm {
|
||||
v4l2_std_id v4l2_id;
|
||||
char name[12];
|
||||
u32 tlg_tvnorm;
|
||||
};
|
||||
|
||||
/* video */
|
||||
int pd_video_init(struct poseidon *);
|
||||
void pd_video_exit(struct poseidon *);
|
||||
int stop_all_video_stream(struct poseidon *);
|
||||
|
||||
/* alsa audio */
|
||||
int poseidon_audio_init(struct poseidon *);
|
||||
int poseidon_audio_free(struct poseidon *);
|
||||
#ifdef CONFIG_PM
|
||||
int pm_alsa_suspend(struct poseidon *);
|
||||
int pm_alsa_resume(struct poseidon *);
|
||||
#endif
|
||||
|
||||
/* dvb */
|
||||
int pd_dvb_usb_device_init(struct poseidon *);
|
||||
void pd_dvb_usb_device_exit(struct poseidon *);
|
||||
void pd_dvb_usb_device_cleanup(struct poseidon *);
|
||||
int pd_dvb_get_adapter_num(struct pd_dvb_adapter *);
|
||||
void dvb_stop_streaming(struct pd_dvb_adapter *);
|
||||
|
||||
/* FM */
|
||||
int poseidon_fm_init(struct poseidon *);
|
||||
int poseidon_fm_exit(struct poseidon *);
|
||||
struct video_device *vdev_init(struct poseidon *, struct video_device *);
|
||||
|
||||
/* vendor command ops */
|
||||
int send_set_req(struct poseidon*, u8, s32, s32*);
|
||||
int send_get_req(struct poseidon*, u8, s32, void*, s32*, s32);
|
||||
s32 set_tuner_mode(struct poseidon*, unsigned char);
|
||||
enum tlg__analog_audio_standard get_audio_std(s32, s32);
|
||||
|
||||
/* bulk urb alloc/free */
|
||||
int alloc_bulk_urbs_generic(struct urb **urb_array, int num,
|
||||
struct usb_device *udev, u8 ep_addr,
|
||||
int buf_size, gfp_t gfp_flags,
|
||||
usb_complete_t complete_fn, void *context);
|
||||
void free_all_urb_generic(struct urb **urb_array, int num);
|
||||
|
||||
/* misc */
|
||||
void poseidon_delete(struct kref *kref);
|
||||
void destroy_video_device(struct video_device **v_dev);
|
||||
extern int country_code;
|
||||
extern int debug_mode;
|
||||
void set_debug_mode(struct video_device *vfd, int debug_mode);
|
||||
|
||||
#define in_hibernation(pd) (pd->msg.event == PM_EVENT_FREEZE)
|
||||
#define get_pm_count(p) (atomic_read(&(p)->interface->pm_usage_cnt))
|
||||
|
||||
#define log(a, ...) printk(KERN_DEBUG "\t[ %s : %.3d ] "a"\n", \
|
||||
__func__, __LINE__, ## __VA_ARGS__)
|
||||
|
||||
/* for power management */
|
||||
#define logpm(pd) do {\
|
||||
if (debug_mode & 0x10)\
|
||||
log();\
|
||||
} while (0)
|
||||
|
||||
#define logs(f) do { \
|
||||
if ((debug_mode & 0x4) && \
|
||||
(f)->type == V4L2_BUF_TYPE_VBI_CAPTURE) \
|
||||
log("type : VBI");\
|
||||
\
|
||||
if ((debug_mode & 0x8) && \
|
||||
(f)->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) \
|
||||
log("type : VIDEO");\
|
||||
} while (0)
|
||||
#endif
|
|
@ -0,0 +1,593 @@
|
|||
#include "pd-common.h"
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/dvb/dmx.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "vendorcmds.h"
|
||||
#include <linux/sched.h>
|
||||
#include <asm/atomic.h>
|
||||
|
||||
static void dvb_urb_cleanup(struct pd_dvb_adapter *pd_dvb);
|
||||
|
||||
static int dvb_bandwidth[][2] = {
|
||||
{ TLG_BW_8, BANDWIDTH_8_MHZ },
|
||||
{ TLG_BW_7, BANDWIDTH_7_MHZ },
|
||||
{ TLG_BW_6, BANDWIDTH_6_MHZ }
|
||||
};
|
||||
static int dvb_bandwidth_length = ARRAY_SIZE(dvb_bandwidth);
|
||||
|
||||
static s32 dvb_start_streaming(struct pd_dvb_adapter *pd_dvb);
|
||||
static int poseidon_check_mode_dvbt(struct poseidon *pd)
|
||||
{
|
||||
s32 ret = 0, cmd_status = 0;
|
||||
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
schedule_timeout(HZ/4);
|
||||
|
||||
ret = usb_set_interface(pd->udev, 0, BULK_ALTERNATE_IFACE);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
ret = set_tuner_mode(pd, TLG_MODE_CAPS_DVB_T);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* signal source */
|
||||
ret = send_set_req(pd, SGNL_SRC_SEL, TLG_SIG_SRC_ANTENNA, &cmd_status);
|
||||
if (ret|cmd_status)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* acquire :
|
||||
* 1 == open
|
||||
* 0 == release
|
||||
*/
|
||||
static int poseidon_ts_bus_ctrl(struct dvb_frontend *fe, int acquire)
|
||||
{
|
||||
struct poseidon *pd = fe->demodulator_priv;
|
||||
struct pd_dvb_adapter *pd_dvb;
|
||||
int ret = 0;
|
||||
|
||||
if (!pd)
|
||||
return -ENODEV;
|
||||
|
||||
pd_dvb = container_of(fe, struct pd_dvb_adapter, dvb_fe);
|
||||
if (acquire) {
|
||||
mutex_lock(&pd->lock);
|
||||
if (pd->state & POSEIDON_STATE_DISCONNECT) {
|
||||
ret = -ENODEV;
|
||||
goto open_out;
|
||||
}
|
||||
|
||||
if (pd->state && !(pd->state & POSEIDON_STATE_DVBT)) {
|
||||
ret = -EBUSY;
|
||||
goto open_out;
|
||||
}
|
||||
|
||||
usb_autopm_get_interface(pd->interface);
|
||||
if (0 == pd->state) {
|
||||
ret = poseidon_check_mode_dvbt(pd);
|
||||
if (ret < 0) {
|
||||
usb_autopm_put_interface(pd->interface);
|
||||
goto open_out;
|
||||
}
|
||||
pd->state |= POSEIDON_STATE_DVBT;
|
||||
pd_dvb->bandwidth = 0;
|
||||
pd_dvb->prev_freq = 0;
|
||||
}
|
||||
atomic_inc(&pd_dvb->users);
|
||||
kref_get(&pd->kref);
|
||||
open_out:
|
||||
mutex_unlock(&pd->lock);
|
||||
} else {
|
||||
dvb_stop_streaming(pd_dvb);
|
||||
|
||||
if (atomic_dec_and_test(&pd_dvb->users)) {
|
||||
mutex_lock(&pd->lock);
|
||||
pd->state &= ~POSEIDON_STATE_DVBT;
|
||||
mutex_unlock(&pd->lock);
|
||||
}
|
||||
kref_put(&pd->kref, poseidon_delete);
|
||||
usb_autopm_put_interface(pd->interface);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void poseidon_fe_release(struct dvb_frontend *fe)
|
||||
{
|
||||
struct poseidon *pd = fe->demodulator_priv;
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
pd->pm_suspend = NULL;
|
||||
pd->pm_resume = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static s32 poseidon_fe_sleep(struct dvb_frontend *fe)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* return true if we can satisfy the conditions, else return false.
|
||||
*/
|
||||
static bool check_scan_ok(__u32 freq, int bandwidth,
|
||||
struct pd_dvb_adapter *adapter)
|
||||
{
|
||||
if (bandwidth < 0)
|
||||
return false;
|
||||
|
||||
if (adapter->prev_freq == freq
|
||||
&& adapter->bandwidth == bandwidth) {
|
||||
long nl = jiffies - adapter->last_jiffies;
|
||||
unsigned int msec ;
|
||||
|
||||
msec = jiffies_to_msecs(abs(nl));
|
||||
return msec > 15000 ? true : false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the firmware delays too long for an invalid frequency.
|
||||
*/
|
||||
static int fw_delay_overflow(struct pd_dvb_adapter *adapter)
|
||||
{
|
||||
long nl = jiffies - adapter->last_jiffies;
|
||||
unsigned int msec ;
|
||||
|
||||
msec = jiffies_to_msecs(abs(nl));
|
||||
return msec > 800 ? true : false;
|
||||
}
|
||||
|
||||
static int poseidon_set_fe(struct dvb_frontend *fe,
|
||||
struct dvb_frontend_parameters *fep)
|
||||
{
|
||||
s32 ret = 0, cmd_status = 0;
|
||||
s32 i, bandwidth = -1;
|
||||
struct poseidon *pd = fe->demodulator_priv;
|
||||
struct pd_dvb_adapter *pd_dvb = &pd->dvb_data;
|
||||
|
||||
if (in_hibernation(pd))
|
||||
return -EBUSY;
|
||||
|
||||
mutex_lock(&pd->lock);
|
||||
for (i = 0; i < dvb_bandwidth_length; i++)
|
||||
if (fep->u.ofdm.bandwidth == dvb_bandwidth[i][1])
|
||||
bandwidth = dvb_bandwidth[i][0];
|
||||
|
||||
if (check_scan_ok(fep->frequency, bandwidth, pd_dvb)) {
|
||||
ret = send_set_req(pd, TUNE_FREQ_SELECT,
|
||||
fep->frequency / 1000, &cmd_status);
|
||||
if (ret | cmd_status) {
|
||||
log("error line");
|
||||
goto front_out;
|
||||
}
|
||||
|
||||
ret = send_set_req(pd, DVBT_BANDW_SEL,
|
||||
bandwidth, &cmd_status);
|
||||
if (ret | cmd_status) {
|
||||
log("error line");
|
||||
goto front_out;
|
||||
}
|
||||
|
||||
ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status);
|
||||
if (ret | cmd_status) {
|
||||
log("error line");
|
||||
goto front_out;
|
||||
}
|
||||
|
||||
/* save the context for future */
|
||||
memcpy(&pd_dvb->fe_param, fep, sizeof(*fep));
|
||||
pd_dvb->bandwidth = bandwidth;
|
||||
pd_dvb->prev_freq = fep->frequency;
|
||||
pd_dvb->last_jiffies = jiffies;
|
||||
}
|
||||
front_out:
|
||||
mutex_unlock(&pd->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int pm_dvb_suspend(struct poseidon *pd)
|
||||
{
|
||||
struct pd_dvb_adapter *pd_dvb = &pd->dvb_data;
|
||||
dvb_stop_streaming(pd_dvb);
|
||||
dvb_urb_cleanup(pd_dvb);
|
||||
msleep(500);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pm_dvb_resume(struct poseidon *pd)
|
||||
{
|
||||
struct pd_dvb_adapter *pd_dvb = &pd->dvb_data;
|
||||
|
||||
poseidon_check_mode_dvbt(pd);
|
||||
msleep(300);
|
||||
poseidon_set_fe(&pd_dvb->dvb_fe, &pd_dvb->fe_param);
|
||||
|
||||
dvb_start_streaming(pd_dvb);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static s32 poseidon_fe_init(struct dvb_frontend *fe)
|
||||
{
|
||||
struct poseidon *pd = fe->demodulator_priv;
|
||||
struct pd_dvb_adapter *pd_dvb = &pd->dvb_data;
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
pd->pm_suspend = pm_dvb_suspend;
|
||||
pd->pm_resume = pm_dvb_resume;
|
||||
#endif
|
||||
memset(&pd_dvb->fe_param, 0,
|
||||
sizeof(struct dvb_frontend_parameters));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int poseidon_get_fe(struct dvb_frontend *fe,
|
||||
struct dvb_frontend_parameters *fep)
|
||||
{
|
||||
struct poseidon *pd = fe->demodulator_priv;
|
||||
struct pd_dvb_adapter *pd_dvb = &pd->dvb_data;
|
||||
|
||||
memcpy(fep, &pd_dvb->fe_param, sizeof(*fep));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int poseidon_fe_get_tune_settings(struct dvb_frontend *fe,
|
||||
struct dvb_frontend_tune_settings *tune)
|
||||
{
|
||||
tune->min_delay_ms = 1000;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int poseidon_read_status(struct dvb_frontend *fe, fe_status_t *stat)
|
||||
{
|
||||
struct poseidon *pd = fe->demodulator_priv;
|
||||
s32 ret = -1, cmd_status;
|
||||
struct tuner_dtv_sig_stat_s status = {};
|
||||
|
||||
if (in_hibernation(pd))
|
||||
return -EBUSY;
|
||||
mutex_lock(&pd->lock);
|
||||
|
||||
ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_DVB_T,
|
||||
&status, &cmd_status, sizeof(status));
|
||||
if (ret | cmd_status) {
|
||||
log("get tuner status error");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (debug_mode)
|
||||
log("P : %d, L %d, LB :%d", status.sig_present,
|
||||
status.sig_locked, status.sig_lock_busy);
|
||||
|
||||
if (status.sig_lock_busy) {
|
||||
goto out;
|
||||
} else if (status.sig_present || status.sig_locked) {
|
||||
*stat |= FE_HAS_LOCK | FE_HAS_SIGNAL | FE_HAS_CARRIER
|
||||
| FE_HAS_SYNC | FE_HAS_VITERBI;
|
||||
} else {
|
||||
if (fw_delay_overflow(&pd->dvb_data))
|
||||
*stat |= FE_TIMEDOUT;
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&pd->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int poseidon_read_ber(struct dvb_frontend *fe, u32 *ber)
|
||||
{
|
||||
struct poseidon *pd = fe->demodulator_priv;
|
||||
struct tuner_ber_rate_s tlg_ber = {};
|
||||
s32 ret = -1, cmd_status;
|
||||
|
||||
mutex_lock(&pd->lock);
|
||||
ret = send_get_req(pd, TUNER_BER_RATE, 0,
|
||||
&tlg_ber, &cmd_status, sizeof(tlg_ber));
|
||||
if (ret | cmd_status)
|
||||
goto out;
|
||||
*ber = tlg_ber.ber_rate;
|
||||
out:
|
||||
mutex_unlock(&pd->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static s32 poseidon_read_signal_strength(struct dvb_frontend *fe, u16 *strength)
|
||||
{
|
||||
struct poseidon *pd = fe->demodulator_priv;
|
||||
struct tuner_dtv_sig_stat_s status = {};
|
||||
s32 ret = 0, cmd_status;
|
||||
|
||||
mutex_lock(&pd->lock);
|
||||
ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_DVB_T,
|
||||
&status, &cmd_status, sizeof(status));
|
||||
if (ret | cmd_status)
|
||||
goto out;
|
||||
if ((status.sig_present || status.sig_locked) && !status.sig_strength)
|
||||
*strength = 0xFFFF;
|
||||
else
|
||||
*strength = status.sig_strength;
|
||||
out:
|
||||
mutex_unlock(&pd->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int poseidon_read_snr(struct dvb_frontend *fe, u16 *snr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int poseidon_read_unc_blocks(struct dvb_frontend *fe, u32 *unc)
|
||||
{
|
||||
*unc = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dvb_frontend_ops poseidon_frontend_ops = {
|
||||
.info = {
|
||||
.name = "Poseidon DVB-T",
|
||||
.type = FE_OFDM,
|
||||
.frequency_min = 174000000,
|
||||
.frequency_max = 862000000,
|
||||
.frequency_stepsize = 62500,/* FIXME */
|
||||
.caps = FE_CAN_INVERSION_AUTO |
|
||||
FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
|
||||
FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
|
||||
FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 |
|
||||
FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO |
|
||||
FE_CAN_GUARD_INTERVAL_AUTO |
|
||||
FE_CAN_RECOVER |
|
||||
FE_CAN_HIERARCHY_AUTO,
|
||||
},
|
||||
|
||||
.release = poseidon_fe_release,
|
||||
|
||||
.init = poseidon_fe_init,
|
||||
.sleep = poseidon_fe_sleep,
|
||||
|
||||
.set_frontend = poseidon_set_fe,
|
||||
.get_frontend = poseidon_get_fe,
|
||||
.get_tune_settings = poseidon_fe_get_tune_settings,
|
||||
|
||||
.read_status = poseidon_read_status,
|
||||
.read_ber = poseidon_read_ber,
|
||||
.read_signal_strength = poseidon_read_signal_strength,
|
||||
.read_snr = poseidon_read_snr,
|
||||
.read_ucblocks = poseidon_read_unc_blocks,
|
||||
|
||||
.ts_bus_ctrl = poseidon_ts_bus_ctrl,
|
||||
};
|
||||
|
||||
static void dvb_urb_irq(struct urb *urb)
|
||||
{
|
||||
struct pd_dvb_adapter *pd_dvb = urb->context;
|
||||
int len = urb->transfer_buffer_length;
|
||||
struct dvb_demux *demux = &pd_dvb->demux;
|
||||
s32 ret;
|
||||
|
||||
if (!pd_dvb->is_streaming || urb->status) {
|
||||
if (urb->status == -EPROTO)
|
||||
goto resend;
|
||||
return;
|
||||
}
|
||||
|
||||
if (urb->actual_length == len)
|
||||
dvb_dmx_swfilter(demux, urb->transfer_buffer, len);
|
||||
else if (urb->actual_length == len - 4) {
|
||||
int offset;
|
||||
u8 *buf = urb->transfer_buffer;
|
||||
|
||||
/*
|
||||
* The packet size is 512,
|
||||
* last packet contains 456 bytes tsp data
|
||||
*/
|
||||
for (offset = 456; offset < len; offset += 512) {
|
||||
if (!strncmp(buf + offset, "DVHS", 4)) {
|
||||
dvb_dmx_swfilter(demux, buf, offset);
|
||||
if (len > offset + 52 + 4) {
|
||||
/*16 bytes trailer + 36 bytes padding */
|
||||
buf += offset + 52;
|
||||
len -= offset + 52 + 4;
|
||||
dvb_dmx_swfilter(demux, buf, len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resend:
|
||||
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (ret)
|
||||
log(" usb_submit_urb failed: error %d", ret);
|
||||
}
|
||||
|
||||
static int dvb_urb_init(struct pd_dvb_adapter *pd_dvb)
|
||||
{
|
||||
if (pd_dvb->urb_array[0])
|
||||
return 0;
|
||||
|
||||
alloc_bulk_urbs_generic(pd_dvb->urb_array, DVB_SBUF_NUM,
|
||||
pd_dvb->pd_device->udev, pd_dvb->ep_addr,
|
||||
DVB_URB_BUF_SIZE, GFP_KERNEL,
|
||||
dvb_urb_irq, pd_dvb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dvb_urb_cleanup(struct pd_dvb_adapter *pd_dvb)
|
||||
{
|
||||
free_all_urb_generic(pd_dvb->urb_array, DVB_SBUF_NUM);
|
||||
}
|
||||
|
||||
static s32 dvb_start_streaming(struct pd_dvb_adapter *pd_dvb)
|
||||
{
|
||||
struct poseidon *pd = pd_dvb->pd_device;
|
||||
int ret = 0;
|
||||
|
||||
if (pd->state & POSEIDON_STATE_DISCONNECT)
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&pd->lock);
|
||||
if (!pd_dvb->is_streaming) {
|
||||
s32 i, cmd_status = 0;
|
||||
/*
|
||||
* Once upon a time, there was a difficult bug lying here.
|
||||
* ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status);
|
||||
*/
|
||||
|
||||
ret = send_set_req(pd, PLAY_SERVICE, 1, &cmd_status);
|
||||
if (ret | cmd_status)
|
||||
goto out;
|
||||
|
||||
ret = dvb_urb_init(pd_dvb);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
pd_dvb->is_streaming = 1;
|
||||
for (i = 0; i < DVB_SBUF_NUM; i++) {
|
||||
ret = usb_submit_urb(pd_dvb->urb_array[i],
|
||||
GFP_KERNEL);
|
||||
if (ret) {
|
||||
log(" submit urb error %d", ret);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&pd->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void dvb_stop_streaming(struct pd_dvb_adapter *pd_dvb)
|
||||
{
|
||||
struct poseidon *pd = pd_dvb->pd_device;
|
||||
|
||||
mutex_lock(&pd->lock);
|
||||
if (pd_dvb->is_streaming) {
|
||||
s32 i, ret, cmd_status = 0;
|
||||
|
||||
pd_dvb->is_streaming = 0;
|
||||
|
||||
for (i = 0; i < DVB_SBUF_NUM; i++)
|
||||
if (pd_dvb->urb_array[i])
|
||||
usb_kill_urb(pd_dvb->urb_array[i]);
|
||||
|
||||
ret = send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP,
|
||||
&cmd_status);
|
||||
if (ret | cmd_status)
|
||||
log("error");
|
||||
}
|
||||
mutex_unlock(&pd->lock);
|
||||
}
|
||||
|
||||
static int pd_start_feed(struct dvb_demux_feed *feed)
|
||||
{
|
||||
struct pd_dvb_adapter *pd_dvb = feed->demux->priv;
|
||||
int ret = 0;
|
||||
|
||||
if (!pd_dvb)
|
||||
return -1;
|
||||
if (atomic_inc_return(&pd_dvb->active_feed) == 1)
|
||||
ret = dvb_start_streaming(pd_dvb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pd_stop_feed(struct dvb_demux_feed *feed)
|
||||
{
|
||||
struct pd_dvb_adapter *pd_dvb = feed->demux->priv;
|
||||
|
||||
if (!pd_dvb)
|
||||
return -1;
|
||||
if (atomic_dec_and_test(&pd_dvb->active_feed))
|
||||
dvb_stop_streaming(pd_dvb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
|
||||
int pd_dvb_usb_device_init(struct poseidon *pd)
|
||||
{
|
||||
struct pd_dvb_adapter *pd_dvb = &pd->dvb_data;
|
||||
struct dvb_demux *dvbdemux;
|
||||
int ret = 0;
|
||||
|
||||
pd_dvb->ep_addr = 0x82;
|
||||
atomic_set(&pd_dvb->users, 0);
|
||||
atomic_set(&pd_dvb->active_feed, 0);
|
||||
pd_dvb->pd_device = pd;
|
||||
|
||||
ret = dvb_register_adapter(&pd_dvb->dvb_adap,
|
||||
"Poseidon dvbt adapter",
|
||||
THIS_MODULE,
|
||||
NULL /* for hibernation correctly*/,
|
||||
adapter_nr);
|
||||
if (ret < 0)
|
||||
goto error1;
|
||||
|
||||
/* register frontend */
|
||||
pd_dvb->dvb_fe.demodulator_priv = pd;
|
||||
memcpy(&pd_dvb->dvb_fe.ops, &poseidon_frontend_ops,
|
||||
sizeof(struct dvb_frontend_ops));
|
||||
ret = dvb_register_frontend(&pd_dvb->dvb_adap, &pd_dvb->dvb_fe);
|
||||
if (ret < 0)
|
||||
goto error2;
|
||||
|
||||
/* register demux device */
|
||||
dvbdemux = &pd_dvb->demux;
|
||||
dvbdemux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
|
||||
dvbdemux->priv = pd_dvb;
|
||||
dvbdemux->feednum = dvbdemux->filternum = 64;
|
||||
dvbdemux->start_feed = pd_start_feed;
|
||||
dvbdemux->stop_feed = pd_stop_feed;
|
||||
dvbdemux->write_to_decoder = NULL;
|
||||
|
||||
ret = dvb_dmx_init(dvbdemux);
|
||||
if (ret < 0)
|
||||
goto error3;
|
||||
|
||||
pd_dvb->dmxdev.filternum = pd_dvb->demux.filternum;
|
||||
pd_dvb->dmxdev.demux = &pd_dvb->demux.dmx;
|
||||
pd_dvb->dmxdev.capabilities = 0;
|
||||
|
||||
ret = dvb_dmxdev_init(&pd_dvb->dmxdev, &pd_dvb->dvb_adap);
|
||||
if (ret < 0)
|
||||
goto error3;
|
||||
return 0;
|
||||
|
||||
error3:
|
||||
dvb_unregister_frontend(&pd_dvb->dvb_fe);
|
||||
error2:
|
||||
dvb_unregister_adapter(&pd_dvb->dvb_adap);
|
||||
error1:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void pd_dvb_usb_device_exit(struct poseidon *pd)
|
||||
{
|
||||
struct pd_dvb_adapter *pd_dvb = &pd->dvb_data;
|
||||
|
||||
while (atomic_read(&pd_dvb->users) != 0
|
||||
|| atomic_read(&pd_dvb->active_feed) != 0) {
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
schedule_timeout(HZ);
|
||||
}
|
||||
dvb_dmxdev_release(&pd_dvb->dmxdev);
|
||||
dvb_unregister_frontend(&pd_dvb->dvb_fe);
|
||||
dvb_unregister_adapter(&pd_dvb->dvb_adap);
|
||||
pd_dvb_usb_device_cleanup(pd);
|
||||
}
|
||||
|
||||
void pd_dvb_usb_device_cleanup(struct poseidon *pd)
|
||||
{
|
||||
struct pd_dvb_adapter *pd_dvb = &pd->dvb_data;
|
||||
|
||||
dvb_urb_cleanup(pd_dvb);
|
||||
}
|
||||
|
||||
int pd_dvb_get_adapter_num(struct pd_dvb_adapter *pd_dvb)
|
||||
{
|
||||
return pd_dvb->dvb_adap.num;
|
||||
}
|
|
@ -0,0 +1,566 @@
|
|||
/*
|
||||
* device driver for Telegent tlg2300 based TV cards
|
||||
*
|
||||
* Author :
|
||||
* Kang Yong <kangyong@telegent.com>
|
||||
* Zhang Xiaobing <xbzhang@telegent.com>
|
||||
* Huang Shijie <zyziii@telegent.com> or <shijie8@gmail.com>
|
||||
*
|
||||
* (c) 2009 Telegent Systems
|
||||
* (c) 2010 Telegent Systems
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/usb/quirks.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/smp_lock.h>
|
||||
|
||||
#include "vendorcmds.h"
|
||||
#include "pd-common.h"
|
||||
|
||||
#define VENDOR_ID 0x1B24
|
||||
#define PRODUCT_ID 0x4001
|
||||
static struct usb_device_id id_table[] = {
|
||||
{ USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID, PRODUCT_ID, 255, 1, 0) },
|
||||
{ USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID, PRODUCT_ID, 255, 1, 1) },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(usb, id_table);
|
||||
|
||||
int debug_mode;
|
||||
module_param(debug_mode, int, 0644);
|
||||
MODULE_PARM_DESC(debug_mode, "0 = disable, 1 = enable, 2 = verbose");
|
||||
|
||||
const char *firmware_name = "tlg2300_firmware.bin";
|
||||
struct usb_driver poseidon_driver;
|
||||
static LIST_HEAD(pd_device_list);
|
||||
|
||||
/*
|
||||
* send set request to USB firmware.
|
||||
*/
|
||||
s32 send_set_req(struct poseidon *pd, u8 cmdid, s32 param, s32 *cmd_status)
|
||||
{
|
||||
s32 ret;
|
||||
s8 data[32] = {};
|
||||
u16 lower_16, upper_16;
|
||||
|
||||
if (pd->state & POSEIDON_STATE_DISCONNECT)
|
||||
return -ENODEV;
|
||||
|
||||
mdelay(30);
|
||||
|
||||
if (param == 0) {
|
||||
upper_16 = lower_16 = 0;
|
||||
} else {
|
||||
/* send 32 bit param as two 16 bit param,little endian */
|
||||
lower_16 = (unsigned short)(param & 0xffff);
|
||||
upper_16 = (unsigned short)((param >> 16) & 0xffff);
|
||||
}
|
||||
ret = usb_control_msg(pd->udev,
|
||||
usb_rcvctrlpipe(pd->udev, 0),
|
||||
REQ_SET_CMD | cmdid,
|
||||
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
|
||||
lower_16,
|
||||
upper_16,
|
||||
&data,
|
||||
sizeof(*cmd_status),
|
||||
USB_CTRL_GET_TIMEOUT);
|
||||
|
||||
if (!ret) {
|
||||
return -ENXIO;
|
||||
} else {
|
||||
/* 1st 4 bytes into cmd_status */
|
||||
memcpy((char *)cmd_status, &(data[0]), sizeof(*cmd_status));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* send get request to Poseidon firmware.
|
||||
*/
|
||||
s32 send_get_req(struct poseidon *pd, u8 cmdid, s32 param,
|
||||
void *buf, s32 *cmd_status, s32 datalen)
|
||||
{
|
||||
s32 ret;
|
||||
s8 data[128] = {};
|
||||
u16 lower_16, upper_16;
|
||||
|
||||
if (pd->state & POSEIDON_STATE_DISCONNECT)
|
||||
return -ENODEV;
|
||||
|
||||
mdelay(30);
|
||||
if (param == 0) {
|
||||
upper_16 = lower_16 = 0;
|
||||
} else {
|
||||
/*send 32 bit param as two 16 bit param, little endian */
|
||||
lower_16 = (unsigned short)(param & 0xffff);
|
||||
upper_16 = (unsigned short)((param >> 16) & 0xffff);
|
||||
}
|
||||
ret = usb_control_msg(pd->udev,
|
||||
usb_rcvctrlpipe(pd->udev, 0),
|
||||
REQ_GET_CMD | cmdid,
|
||||
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
|
||||
lower_16,
|
||||
upper_16,
|
||||
&data,
|
||||
(datalen + sizeof(*cmd_status)),
|
||||
USB_CTRL_GET_TIMEOUT);
|
||||
|
||||
if (ret < 0) {
|
||||
return -ENXIO;
|
||||
} else {
|
||||
/* 1st 4 bytes into cmd_status, remaining data into cmd_data */
|
||||
memcpy((char *)cmd_status, &data[0], sizeof(*cmd_status));
|
||||
memcpy((char *)buf, &data[sizeof(*cmd_status)], datalen);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pm_notifier_block(struct notifier_block *nb,
|
||||
unsigned long event, void *dummy)
|
||||
{
|
||||
struct poseidon *pd = NULL;
|
||||
struct list_head *node, *next;
|
||||
|
||||
switch (event) {
|
||||
case PM_POST_HIBERNATION:
|
||||
list_for_each_safe(node, next, &pd_device_list) {
|
||||
struct usb_device *udev;
|
||||
struct usb_interface *iface;
|
||||
int rc = 0;
|
||||
|
||||
pd = container_of(node, struct poseidon, device_list);
|
||||
udev = pd->udev;
|
||||
iface = pd->interface;
|
||||
|
||||
/* It will cause the system to reload the firmware */
|
||||
rc = usb_lock_device_for_reset(udev, iface);
|
||||
if (rc >= 0) {
|
||||
usb_reset_device(udev);
|
||||
usb_unlock_device(udev);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
log("event :%ld\n", event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct notifier_block pm_notifer = {
|
||||
.notifier_call = pm_notifier_block,
|
||||
};
|
||||
|
||||
int set_tuner_mode(struct poseidon *pd, unsigned char mode)
|
||||
{
|
||||
s32 ret, cmd_status;
|
||||
|
||||
if (pd->state & POSEIDON_STATE_DISCONNECT)
|
||||
return -ENODEV;
|
||||
|
||||
ret = send_set_req(pd, TUNE_MODE_SELECT, mode, &cmd_status);
|
||||
if (ret || cmd_status)
|
||||
return -ENXIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum tlg__analog_audio_standard get_audio_std(s32 mode, s32 country_code)
|
||||
{
|
||||
s32 nicam[] = {27, 32, 33, 34, 36, 44, 45, 46, 47, 48, 64,
|
||||
65, 86, 351, 352, 353, 354, 358, 372, 852, 972};
|
||||
s32 btsc[] = {1, 52, 54, 55, 886};
|
||||
s32 eiaj[] = {81};
|
||||
s32 i;
|
||||
|
||||
if (mode == TLG_MODE_FM_RADIO) {
|
||||
if (country_code == 1)
|
||||
return TLG_TUNE_ASTD_FM_US;
|
||||
else
|
||||
return TLG_TUNE_ASTD_FM_EUR;
|
||||
} else if (mode == TLG_MODE_ANALOG_TV_UNCOMP) {
|
||||
for (i = 0; i < sizeof(nicam) / sizeof(s32); i++) {
|
||||
if (country_code == nicam[i])
|
||||
return TLG_TUNE_ASTD_NICAM;
|
||||
}
|
||||
|
||||
for (i = 0; i < sizeof(btsc) / sizeof(s32); i++) {
|
||||
if (country_code == btsc[i])
|
||||
return TLG_TUNE_ASTD_BTSC;
|
||||
}
|
||||
|
||||
for (i = 0; i < sizeof(eiaj) / sizeof(s32); i++) {
|
||||
if (country_code == eiaj[i])
|
||||
return TLG_TUNE_ASTD_EIAJ;
|
||||
}
|
||||
|
||||
return TLG_TUNE_ASTD_A2;
|
||||
} else {
|
||||
return TLG_TUNE_ASTD_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void poseidon_delete(struct kref *kref)
|
||||
{
|
||||
struct poseidon *pd = container_of(kref, struct poseidon, kref);
|
||||
|
||||
if (!pd)
|
||||
return;
|
||||
list_del_init(&pd->device_list);
|
||||
|
||||
pd_dvb_usb_device_cleanup(pd);
|
||||
/* clean_audio_data(&pd->audio_data);*/
|
||||
|
||||
if (pd->udev) {
|
||||
usb_put_dev(pd->udev);
|
||||
pd->udev = NULL;
|
||||
}
|
||||
if (pd->interface) {
|
||||
usb_put_intf(pd->interface);
|
||||
pd->interface = NULL;
|
||||
}
|
||||
kfree(pd);
|
||||
log();
|
||||
}
|
||||
|
||||
static int firmware_download(struct usb_device *udev)
|
||||
{
|
||||
int ret = 0, actual_length;
|
||||
const struct firmware *fw = NULL;
|
||||
void *fwbuf = NULL;
|
||||
size_t fwlength = 0, offset;
|
||||
size_t max_packet_size;
|
||||
|
||||
ret = request_firmware(&fw, firmware_name, &udev->dev);
|
||||
if (ret) {
|
||||
log("download err : %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
fwlength = fw->size;
|
||||
|
||||
fwbuf = kzalloc(fwlength, GFP_KERNEL);
|
||||
if (!fwbuf) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
memcpy(fwbuf, fw->data, fwlength);
|
||||
|
||||
max_packet_size = udev->ep_out[0x1]->desc.wMaxPacketSize;
|
||||
log("\t\t download size : %d", (int)max_packet_size);
|
||||
|
||||
for (offset = 0; offset < fwlength; offset += max_packet_size) {
|
||||
actual_length = 0;
|
||||
ret = usb_bulk_msg(udev,
|
||||
usb_sndbulkpipe(udev, 0x01), /* ep 1 */
|
||||
fwbuf + offset,
|
||||
min(max_packet_size, fwlength - offset),
|
||||
&actual_length,
|
||||
HZ * 10);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
kfree(fwbuf);
|
||||
out:
|
||||
release_firmware(fw);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
/* one-to-one map : poseidon{} <----> usb_device{}'s port */
|
||||
static inline void set_map_flags(struct poseidon *pd, struct usb_device *udev)
|
||||
{
|
||||
pd->portnum = udev->portnum;
|
||||
}
|
||||
|
||||
static inline int get_autopm_ref(struct poseidon *pd)
|
||||
{
|
||||
return pd->video_data.users + pd->vbi_data.users + pd->audio.users
|
||||
+ atomic_read(&pd->dvb_data.users) + pd->radio_data.users;
|
||||
}
|
||||
|
||||
/* fixup something for poseidon */
|
||||
static inline struct poseidon *fixup(struct poseidon *pd)
|
||||
{
|
||||
int count;
|
||||
|
||||
/* old udev and interface have gone, so put back reference . */
|
||||
count = get_autopm_ref(pd);
|
||||
log("count : %d, ref count : %d", count, get_pm_count(pd));
|
||||
while (count--)
|
||||
usb_autopm_put_interface(pd->interface);
|
||||
/*usb_autopm_set_interface(pd->interface); */
|
||||
|
||||
usb_put_dev(pd->udev);
|
||||
usb_put_intf(pd->interface);
|
||||
log("event : %d\n", pd->msg.event);
|
||||
return pd;
|
||||
}
|
||||
|
||||
static struct poseidon *find_old_poseidon(struct usb_device *udev)
|
||||
{
|
||||
struct poseidon *pd;
|
||||
|
||||
list_for_each_entry(pd, &pd_device_list, device_list) {
|
||||
if (pd->portnum == udev->portnum && in_hibernation(pd))
|
||||
return fixup(pd);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Is the card working now ? */
|
||||
static inline int is_working(struct poseidon *pd)
|
||||
{
|
||||
return get_pm_count(pd) > 0;
|
||||
}
|
||||
|
||||
static inline struct poseidon *get_pd(struct usb_interface *intf)
|
||||
{
|
||||
return usb_get_intfdata(intf);
|
||||
}
|
||||
|
||||
static int poseidon_suspend(struct usb_interface *intf, pm_message_t msg)
|
||||
{
|
||||
struct poseidon *pd = get_pd(intf);
|
||||
|
||||
if (!pd)
|
||||
return 0;
|
||||
if (!is_working(pd)) {
|
||||
if (get_pm_count(pd) <= 0 && !in_hibernation(pd)) {
|
||||
pd->msg.event = PM_EVENT_AUTO_SUSPEND;
|
||||
pd->pm_resume = NULL; /* a good guard */
|
||||
printk(KERN_DEBUG "\n\t+ TLG2300 auto suspend +\n\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
pd->msg = msg; /* save it here */
|
||||
logpm(pd);
|
||||
return pd->pm_suspend ? pd->pm_suspend(pd) : 0;
|
||||
}
|
||||
|
||||
static int poseidon_resume(struct usb_interface *intf)
|
||||
{
|
||||
struct poseidon *pd = get_pd(intf);
|
||||
|
||||
if (!pd)
|
||||
return 0;
|
||||
printk(KERN_DEBUG "\n\t ++ TLG2300 resume ++\n\n");
|
||||
|
||||
if (!is_working(pd)) {
|
||||
if (PM_EVENT_AUTO_SUSPEND == pd->msg.event)
|
||||
pd->msg = PMSG_ON;
|
||||
return 0;
|
||||
}
|
||||
if (in_hibernation(pd)) {
|
||||
logpm(pd);
|
||||
return 0;
|
||||
}
|
||||
logpm(pd);
|
||||
return pd->pm_resume ? pd->pm_resume(pd) : 0;
|
||||
}
|
||||
|
||||
static void hibernation_resume(struct work_struct *w)
|
||||
{
|
||||
struct poseidon *pd = container_of(w, struct poseidon, pm_work);
|
||||
int count;
|
||||
|
||||
pd->msg.event = 0; /* clear it here */
|
||||
pd->state &= ~POSEIDON_STATE_DISCONNECT;
|
||||
|
||||
/* set the new interface's reference */
|
||||
count = get_autopm_ref(pd);
|
||||
while (count--)
|
||||
usb_autopm_get_interface(pd->interface);
|
||||
|
||||
/* resume the context */
|
||||
logpm(pd);
|
||||
if (pd->pm_resume)
|
||||
pd->pm_resume(pd);
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool check_firmware(struct usb_device *udev, int *down_firmware)
|
||||
{
|
||||
void *buf;
|
||||
int ret;
|
||||
struct cmd_firmware_vers_s *cmd_firm;
|
||||
|
||||
buf = kzalloc(sizeof(*cmd_firm) + sizeof(u32), GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
ret = usb_control_msg(udev,
|
||||
usb_rcvctrlpipe(udev, 0),
|
||||
REQ_GET_CMD | GET_FW_ID,
|
||||
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
|
||||
0,
|
||||
0,
|
||||
buf,
|
||||
sizeof(*cmd_firm) + sizeof(u32),
|
||||
USB_CTRL_GET_TIMEOUT);
|
||||
kfree(buf);
|
||||
|
||||
if (ret < 0) {
|
||||
*down_firmware = 1;
|
||||
return firmware_download(udev);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int poseidon_probe(struct usb_interface *interface,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
struct usb_device *udev = interface_to_usbdev(interface);
|
||||
struct poseidon *pd = NULL;
|
||||
int ret = 0;
|
||||
int new_one = 0;
|
||||
|
||||
/* download firmware */
|
||||
check_firmware(udev, &ret);
|
||||
if (ret)
|
||||
return 0;
|
||||
|
||||
/* Do I recovery from the hibernate ? */
|
||||
pd = find_old_poseidon(udev);
|
||||
if (!pd) {
|
||||
pd = kzalloc(sizeof(*pd), GFP_KERNEL);
|
||||
if (!pd)
|
||||
return -ENOMEM;
|
||||
kref_init(&pd->kref);
|
||||
set_map_flags(pd, udev);
|
||||
new_one = 1;
|
||||
}
|
||||
|
||||
pd->udev = usb_get_dev(udev);
|
||||
pd->interface = usb_get_intf(interface);
|
||||
usb_set_intfdata(interface, pd);
|
||||
|
||||
if (new_one) {
|
||||
struct device *dev = &interface->dev;
|
||||
|
||||
logpm(pd);
|
||||
pd->country_code = 86;
|
||||
mutex_init(&pd->lock);
|
||||
|
||||
/* register v4l2 device */
|
||||
snprintf(pd->v4l2_dev.name, sizeof(pd->v4l2_dev.name), "%s %s",
|
||||
dev->driver->name, dev_name(dev));
|
||||
ret = v4l2_device_register(NULL, &pd->v4l2_dev);
|
||||
|
||||
/* register devices in directory /dev */
|
||||
ret = pd_video_init(pd);
|
||||
poseidon_audio_init(pd);
|
||||
poseidon_fm_init(pd);
|
||||
pd_dvb_usb_device_init(pd);
|
||||
|
||||
INIT_LIST_HEAD(&pd->device_list);
|
||||
list_add_tail(&pd->device_list, &pd_device_list);
|
||||
}
|
||||
|
||||
device_init_wakeup(&udev->dev, 1);
|
||||
#ifdef CONFIG_PM
|
||||
pd->udev->autosuspend_disabled = 0;
|
||||
pd->udev->autosuspend_delay = HZ * PM_SUSPEND_DELAY;
|
||||
|
||||
if (in_hibernation(pd)) {
|
||||
INIT_WORK(&pd->pm_work, hibernation_resume);
|
||||
schedule_work(&pd->pm_work);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void poseidon_disconnect(struct usb_interface *interface)
|
||||
{
|
||||
struct poseidon *pd = get_pd(interface);
|
||||
|
||||
if (!pd)
|
||||
return;
|
||||
logpm(pd);
|
||||
if (in_hibernation(pd))
|
||||
return;
|
||||
|
||||
mutex_lock(&pd->lock);
|
||||
pd->state |= POSEIDON_STATE_DISCONNECT;
|
||||
mutex_unlock(&pd->lock);
|
||||
|
||||
/* stop urb transferring */
|
||||
stop_all_video_stream(pd);
|
||||
dvb_stop_streaming(&pd->dvb_data);
|
||||
|
||||
/*unregister v4l2 device */
|
||||
v4l2_device_unregister(&pd->v4l2_dev);
|
||||
|
||||
lock_kernel();
|
||||
{
|
||||
pd_dvb_usb_device_exit(pd);
|
||||
poseidon_fm_exit(pd);
|
||||
|
||||
poseidon_audio_free(pd);
|
||||
pd_video_exit(pd);
|
||||
}
|
||||
unlock_kernel();
|
||||
|
||||
usb_set_intfdata(interface, NULL);
|
||||
kref_put(&pd->kref, poseidon_delete);
|
||||
}
|
||||
|
||||
struct usb_driver poseidon_driver = {
|
||||
.name = "poseidon",
|
||||
.probe = poseidon_probe,
|
||||
.disconnect = poseidon_disconnect,
|
||||
.id_table = id_table,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = poseidon_suspend,
|
||||
.resume = poseidon_resume,
|
||||
#endif
|
||||
.supports_autosuspend = 1,
|
||||
};
|
||||
|
||||
static int __init poseidon_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = usb_register(&poseidon_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
register_pm_notifier(&pm_notifer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit poseidon_exit(void)
|
||||
{
|
||||
log();
|
||||
unregister_pm_notifier(&pm_notifer);
|
||||
usb_deregister(&poseidon_driver);
|
||||
}
|
||||
|
||||
module_init(poseidon_init);
|
||||
module_exit(poseidon_exit);
|
||||
|
||||
MODULE_AUTHOR("Telegent Systems");
|
||||
MODULE_DESCRIPTION("For tlg2300-based USB device ");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,351 @@
|
|||
#include <linux/init.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <media/v4l2-dev.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <media/v4l2-ioctl.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include "pd-common.h"
|
||||
#include "vendorcmds.h"
|
||||
|
||||
static int set_frequency(struct poseidon *p, __u32 frequency);
|
||||
static int poseidon_fm_close(struct file *filp);
|
||||
static int poseidon_fm_open(struct file *filp);
|
||||
|
||||
#define TUNER_FREQ_MIN_FM 76000000
|
||||
#define TUNER_FREQ_MAX_FM 108000000
|
||||
|
||||
static int poseidon_check_mode_radio(struct poseidon *p)
|
||||
{
|
||||
int ret, radiomode;
|
||||
u32 status;
|
||||
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
schedule_timeout(HZ/2);
|
||||
ret = usb_set_interface(p->udev, 0, BULK_ALTERNATE_IFACE);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = set_tuner_mode(p, TLG_MODE_FM_RADIO);
|
||||
if (ret != 0)
|
||||
goto out;
|
||||
|
||||
ret = send_set_req(p, SGNL_SRC_SEL, TLG_SIG_SRC_ANTENNA, &status);
|
||||
radiomode = get_audio_std(TLG_MODE_FM_RADIO, p->country_code);
|
||||
ret = send_set_req(p, TUNER_AUD_ANA_STD, radiomode, &status);
|
||||
ret |= send_set_req(p, TUNER_AUD_MODE,
|
||||
TLG_TUNE_TVAUDIO_MODE_STEREO, &status);
|
||||
ret |= send_set_req(p, AUDIO_SAMPLE_RATE_SEL,
|
||||
ATV_AUDIO_RATE_48K, &status);
|
||||
ret |= send_set_req(p, TUNE_FREQ_SELECT, TUNER_FREQ_MIN_FM, &status);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int pm_fm_suspend(struct poseidon *p)
|
||||
{
|
||||
logpm(p);
|
||||
pm_alsa_suspend(p);
|
||||
usb_set_interface(p->udev, 0, 0);
|
||||
msleep(300);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pm_fm_resume(struct poseidon *p)
|
||||
{
|
||||
logpm(p);
|
||||
poseidon_check_mode_radio(p);
|
||||
set_frequency(p, p->radio_data.fm_freq);
|
||||
pm_alsa_resume(p);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int poseidon_fm_open(struct file *filp)
|
||||
{
|
||||
struct video_device *vfd = video_devdata(filp);
|
||||
struct poseidon *p = video_get_drvdata(vfd);
|
||||
int ret = 0;
|
||||
|
||||
if (!p)
|
||||
return -1;
|
||||
|
||||
mutex_lock(&p->lock);
|
||||
if (p->state & POSEIDON_STATE_DISCONNECT) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (p->state && !(p->state & POSEIDON_STATE_FM)) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
usb_autopm_get_interface(p->interface);
|
||||
if (0 == p->state) {
|
||||
p->country_code = country_code;
|
||||
set_debug_mode(vfd, debug_mode);
|
||||
|
||||
ret = poseidon_check_mode_radio(p);
|
||||
if (ret < 0) {
|
||||
usb_autopm_put_interface(p->interface);
|
||||
goto out;
|
||||
}
|
||||
p->state |= POSEIDON_STATE_FM;
|
||||
}
|
||||
p->radio_data.users++;
|
||||
kref_get(&p->kref);
|
||||
filp->private_data = p;
|
||||
out:
|
||||
mutex_unlock(&p->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int poseidon_fm_close(struct file *filp)
|
||||
{
|
||||
struct poseidon *p = filp->private_data;
|
||||
struct radio_data *fm = &p->radio_data;
|
||||
uint32_t status;
|
||||
|
||||
mutex_lock(&p->lock);
|
||||
fm->users--;
|
||||
if (0 == fm->users)
|
||||
p->state &= ~POSEIDON_STATE_FM;
|
||||
|
||||
if (fm->is_radio_streaming && filp == p->file_for_stream) {
|
||||
fm->is_radio_streaming = 0;
|
||||
send_set_req(p, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP, &status);
|
||||
}
|
||||
usb_autopm_put_interface(p->interface);
|
||||
mutex_unlock(&p->lock);
|
||||
|
||||
kref_put(&p->kref, poseidon_delete);
|
||||
filp->private_data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vidioc_querycap(struct file *file, void *priv,
|
||||
struct v4l2_capability *v)
|
||||
{
|
||||
struct poseidon *p = file->private_data;
|
||||
|
||||
strlcpy(v->driver, "tele-radio", sizeof(v->driver));
|
||||
strlcpy(v->card, "Telegent Poseidon", sizeof(v->card));
|
||||
usb_make_path(p->udev, v->bus_info, sizeof(v->bus_info));
|
||||
v->version = KERNEL_VERSION(0, 0, 1);
|
||||
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct v4l2_file_operations poseidon_fm_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = poseidon_fm_open,
|
||||
.release = poseidon_fm_close,
|
||||
.ioctl = video_ioctl2,
|
||||
};
|
||||
|
||||
int tlg_fm_vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *vt)
|
||||
{
|
||||
struct tuner_fm_sig_stat_s fm_stat = {};
|
||||
int ret, status, count = 5;
|
||||
struct poseidon *p = file->private_data;
|
||||
|
||||
if (vt->index != 0)
|
||||
return -EINVAL;
|
||||
|
||||
vt->type = V4L2_TUNER_RADIO;
|
||||
vt->capability = V4L2_TUNER_CAP_STEREO;
|
||||
vt->rangelow = TUNER_FREQ_MIN_FM / 62500;
|
||||
vt->rangehigh = TUNER_FREQ_MAX_FM / 62500;
|
||||
vt->rxsubchans = V4L2_TUNER_SUB_STEREO;
|
||||
vt->audmode = V4L2_TUNER_MODE_STEREO;
|
||||
vt->signal = 0;
|
||||
vt->afc = 0;
|
||||
|
||||
mutex_lock(&p->lock);
|
||||
ret = send_get_req(p, TUNER_STATUS, TLG_MODE_FM_RADIO,
|
||||
&fm_stat, &status, sizeof(fm_stat));
|
||||
|
||||
while (fm_stat.sig_lock_busy && count-- && !ret) {
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
schedule_timeout(HZ);
|
||||
|
||||
ret = send_get_req(p, TUNER_STATUS, TLG_MODE_FM_RADIO,
|
||||
&fm_stat, &status, sizeof(fm_stat));
|
||||
}
|
||||
mutex_unlock(&p->lock);
|
||||
|
||||
if (ret || status) {
|
||||
vt->signal = 0;
|
||||
} else if ((fm_stat.sig_present || fm_stat.sig_locked)
|
||||
&& fm_stat.sig_strength == 0) {
|
||||
vt->signal = 0xffff;
|
||||
} else
|
||||
vt->signal = (fm_stat.sig_strength * 255 / 10) << 8;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fm_get_freq(struct file *file, void *priv, struct v4l2_frequency *argp)
|
||||
{
|
||||
struct poseidon *p = file->private_data;
|
||||
|
||||
argp->frequency = p->radio_data.fm_freq;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_frequency(struct poseidon *p, __u32 frequency)
|
||||
{
|
||||
__u32 freq ;
|
||||
int ret, status, radiomode;
|
||||
|
||||
mutex_lock(&p->lock);
|
||||
|
||||
radiomode = get_audio_std(TLG_MODE_FM_RADIO, p->country_code);
|
||||
/*NTSC 8,PAL 2 */
|
||||
ret = send_set_req(p, TUNER_AUD_ANA_STD, radiomode, &status);
|
||||
|
||||
freq = (frequency * 125) * 500 / 1000;/* kHZ */
|
||||
if (freq < TUNER_FREQ_MIN_FM/1000 || freq > TUNER_FREQ_MAX_FM/1000) {
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = send_set_req(p, TUNE_FREQ_SELECT, freq, &status);
|
||||
if (ret < 0)
|
||||
goto error ;
|
||||
ret = send_set_req(p, TAKE_REQUEST, 0, &status);
|
||||
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
schedule_timeout(HZ/4);
|
||||
if (!p->radio_data.is_radio_streaming) {
|
||||
ret = send_set_req(p, TAKE_REQUEST, 0, &status);
|
||||
ret = send_set_req(p, PLAY_SERVICE,
|
||||
TLG_TUNE_PLAY_SVC_START, &status);
|
||||
p->radio_data.is_radio_streaming = 1;
|
||||
}
|
||||
p->radio_data.fm_freq = frequency;
|
||||
error:
|
||||
mutex_unlock(&p->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int fm_set_freq(struct file *file, void *priv, struct v4l2_frequency *argp)
|
||||
{
|
||||
struct poseidon *p = file->private_data;
|
||||
|
||||
p->file_for_stream = file;
|
||||
#ifdef CONFIG_PM
|
||||
p->pm_suspend = pm_fm_suspend;
|
||||
p->pm_resume = pm_fm_resume;
|
||||
#endif
|
||||
return set_frequency(p, argp->frequency);
|
||||
}
|
||||
|
||||
int tlg_fm_vidioc_g_ctrl(struct file *file, void *priv,
|
||||
struct v4l2_control *arg)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tlg_fm_vidioc_exts_ctrl(struct file *file, void *fh,
|
||||
struct v4l2_ext_controls *a)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tlg_fm_vidioc_s_ctrl(struct file *file, void *priv,
|
||||
struct v4l2_control *arg)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tlg_fm_vidioc_queryctrl(struct file *file, void *priv,
|
||||
struct v4l2_queryctrl *arg)
|
||||
{
|
||||
arg->minimum = 0;
|
||||
arg->maximum = 65535;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *vt)
|
||||
{
|
||||
return vt->index > 0 ? -EINVAL : 0;
|
||||
}
|
||||
static int vidioc_s_audio(struct file *file, void *priv, struct v4l2_audio *va)
|
||||
{
|
||||
return (va->index != 0) ? -EINVAL : 0;
|
||||
}
|
||||
|
||||
static int vidioc_g_audio(struct file *file, void *priv, struct v4l2_audio *a)
|
||||
{
|
||||
a->index = 0;
|
||||
a->mode = 0;
|
||||
a->capability = V4L2_AUDCAP_STEREO;
|
||||
strcpy(a->name, "Radio");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vidioc_s_input(struct file *filp, void *priv, u32 i)
|
||||
{
|
||||
return (i != 0) ? -EINVAL : 0;
|
||||
}
|
||||
|
||||
static int vidioc_g_input(struct file *filp, void *priv, u32 *i)
|
||||
{
|
||||
return (*i != 0) ? -EINVAL : 0;
|
||||
}
|
||||
|
||||
static const struct v4l2_ioctl_ops poseidon_fm_ioctl_ops = {
|
||||
.vidioc_querycap = vidioc_querycap,
|
||||
.vidioc_g_audio = vidioc_g_audio,
|
||||
.vidioc_s_audio = vidioc_s_audio,
|
||||
.vidioc_g_input = vidioc_g_input,
|
||||
.vidioc_s_input = vidioc_s_input,
|
||||
.vidioc_queryctrl = tlg_fm_vidioc_queryctrl,
|
||||
.vidioc_g_ctrl = tlg_fm_vidioc_g_ctrl,
|
||||
.vidioc_s_ctrl = tlg_fm_vidioc_s_ctrl,
|
||||
.vidioc_s_ext_ctrls = tlg_fm_vidioc_exts_ctrl,
|
||||
.vidioc_s_tuner = vidioc_s_tuner,
|
||||
.vidioc_g_tuner = tlg_fm_vidioc_g_tuner,
|
||||
.vidioc_g_frequency = fm_get_freq,
|
||||
.vidioc_s_frequency = fm_set_freq,
|
||||
};
|
||||
|
||||
static struct video_device poseidon_fm_template = {
|
||||
.name = "Telegent-Radio",
|
||||
.fops = &poseidon_fm_fops,
|
||||
.minor = -1,
|
||||
.release = video_device_release,
|
||||
.ioctl_ops = &poseidon_fm_ioctl_ops,
|
||||
};
|
||||
|
||||
int poseidon_fm_init(struct poseidon *p)
|
||||
{
|
||||
struct video_device *fm_dev;
|
||||
|
||||
fm_dev = vdev_init(p, &poseidon_fm_template);
|
||||
if (fm_dev == NULL)
|
||||
return -1;
|
||||
|
||||
if (video_register_device(fm_dev, VFL_TYPE_RADIO, -1) < 0) {
|
||||
video_device_release(fm_dev);
|
||||
return -1;
|
||||
}
|
||||
p->radio_data.fm_dev = fm_dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int poseidon_fm_exit(struct poseidon *p)
|
||||
{
|
||||
destroy_video_device(&p->radio_data.fm_dev);
|
||||
return 0;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,243 @@
|
|||
#ifndef VENDOR_CMD_H_
|
||||
#define VENDOR_CMD_H_
|
||||
|
||||
#define BULK_ALTERNATE_IFACE (2)
|
||||
#define ISO_3K_BULK_ALTERNATE_IFACE (1)
|
||||
#define REQ_SET_CMD (0X00)
|
||||
#define REQ_GET_CMD (0X80)
|
||||
|
||||
enum tlg__analog_audio_standard {
|
||||
TLG_TUNE_ASTD_NONE = 0x00000000,
|
||||
TLG_TUNE_ASTD_A2 = 0x00000001,
|
||||
TLG_TUNE_ASTD_NICAM = 0x00000002,
|
||||
TLG_TUNE_ASTD_EIAJ = 0x00000004,
|
||||
TLG_TUNE_ASTD_BTSC = 0x00000008,
|
||||
TLG_TUNE_ASTD_FM_US = 0x00000010,
|
||||
TLG_TUNE_ASTD_FM_EUR = 0x00000020,
|
||||
TLG_TUNE_ASTD_ALL = 0x0000003f
|
||||
};
|
||||
|
||||
/*
|
||||
* identifiers for Custom Parameter messages.
|
||||
* @typedef cmd_custom_param_id_t
|
||||
*/
|
||||
enum cmd_custom_param_id {
|
||||
CUST_PARM_ID_NONE = 0x00,
|
||||
CUST_PARM_ID_BRIGHTNESS_CTRL = 0x01,
|
||||
CUST_PARM_ID_CONTRAST_CTRL = 0x02,
|
||||
CUST_PARM_ID_HUE_CTRL = 0x03,
|
||||
CUST_PARM_ID_SATURATION_CTRL = 0x04,
|
||||
CUST_PARM_ID_AUDIO_SNR_THRESHOLD = 0x10,
|
||||
CUST_PARM_ID_AUDIO_AGC_THRESHOLD = 0x11,
|
||||
CUST_PARM_ID_MAX
|
||||
};
|
||||
|
||||
struct tuner_custom_parameter_s {
|
||||
uint16_t param_id; /* Parameter identifier */
|
||||
uint16_t param_value; /* Parameter value */
|
||||
};
|
||||
|
||||
struct tuner_ber_rate_s {
|
||||
uint32_t ber_rate; /* BER sample rate in seconds */
|
||||
};
|
||||
|
||||
struct tuner_atv_sig_stat_s {
|
||||
uint32_t sig_present;
|
||||
uint32_t sig_locked;
|
||||
uint32_t sig_lock_busy;
|
||||
uint32_t sig_strength; /* milliDb */
|
||||
uint32_t tv_audio_chan; /* mono/stereo/sap*/
|
||||
uint32_t mvision_stat; /* macrovision status */
|
||||
};
|
||||
|
||||
struct tuner_dtv_sig_stat_s {
|
||||
uint32_t sig_present; /* Boolean*/
|
||||
uint32_t sig_locked; /* Boolean */
|
||||
uint32_t sig_lock_busy; /* Boolean (Can this time-out?) */
|
||||
uint32_t sig_strength; /* milliDb*/
|
||||
};
|
||||
|
||||
struct tuner_fm_sig_stat_s {
|
||||
uint32_t sig_present; /* Boolean*/
|
||||
uint32_t sig_locked; /* Boolean */
|
||||
uint32_t sig_lock_busy; /* Boolean */
|
||||
uint32_t sig_stereo_mono;/* TBD*/
|
||||
uint32_t sig_strength; /* milliDb*/
|
||||
};
|
||||
|
||||
enum _tag_tlg_tune_srv_cmd {
|
||||
TLG_TUNE_PLAY_SVC_START = 1,
|
||||
TLG_TUNE_PLAY_SVC_STOP
|
||||
};
|
||||
|
||||
enum _tag_tune_atv_audio_mode_caps {
|
||||
TLG_TUNE_TVAUDIO_MODE_MONO = 0x00000001,
|
||||
TLG_TUNE_TVAUDIO_MODE_STEREO = 0x00000002,
|
||||
TLG_TUNE_TVAUDIO_MODE_LANG_A = 0x00000010,/* Primary language*/
|
||||
TLG_TUNE_TVAUDIO_MODE_LANG_B = 0x00000020,/* 2nd avail language*/
|
||||
TLG_TUNE_TVAUDIO_MODE_LANG_C = 0x00000040
|
||||
};
|
||||
|
||||
|
||||
enum _tag_tuner_atv_audio_rates {
|
||||
ATV_AUDIO_RATE_NONE = 0x00,/* Audio not supported*/
|
||||
ATV_AUDIO_RATE_32K = 0x01,/* Audio rate = 32 KHz*/
|
||||
ATV_AUDIO_RATE_48K = 0x02, /* Audio rate = 48 KHz*/
|
||||
ATV_AUDIO_RATE_31_25K = 0x04 /* Audio rate = 31.25KHz */
|
||||
};
|
||||
|
||||
enum _tag_tune_atv_vid_res_caps {
|
||||
TLG_TUNE_VID_RES_NONE = 0x00000000,
|
||||
TLG_TUNE_VID_RES_720 = 0x00000001,
|
||||
TLG_TUNE_VID_RES_704 = 0x00000002,
|
||||
TLG_TUNE_VID_RES_360 = 0x00000004
|
||||
};
|
||||
|
||||
enum _tag_tuner_analog_video_format {
|
||||
TLG_TUNER_VID_FORMAT_YUV = 0x00000001,
|
||||
TLG_TUNER_VID_FORMAT_YCRCB = 0x00000002,
|
||||
TLG_TUNER_VID_FORMAT_RGB_565 = 0x00000004,
|
||||
};
|
||||
|
||||
enum tlg_ext_audio_support {
|
||||
TLG_EXT_AUDIO_NONE = 0x00,/* No external audio input supported */
|
||||
TLG_EXT_AUDIO_LR = 0x01/* LR external audio inputs supported*/
|
||||
};
|
||||
|
||||
enum {
|
||||
TLG_MODE_NONE = 0x00, /* No Mode specified*/
|
||||
TLG_MODE_ANALOG_TV = 0x01, /* Analog Television mode*/
|
||||
TLG_MODE_ANALOG_TV_UNCOMP = 0x01, /* Analog Television mode*/
|
||||
TLG_MODE_ANALOG_TV_COMP = 0x02, /* Analog TV mode (compressed)*/
|
||||
TLG_MODE_FM_RADIO = 0x04, /* FM Radio mode*/
|
||||
TLG_MODE_DVB_T = 0x08, /* Digital TV (DVB-T)*/
|
||||
};
|
||||
|
||||
enum tlg_signal_sources_t {
|
||||
TLG_SIG_SRC_NONE = 0x00,/* Signal source not specified */
|
||||
TLG_SIG_SRC_ANTENNA = 0x01,/* Signal src is: Antenna */
|
||||
TLG_SIG_SRC_CABLE = 0x02,/* Signal src is: Coax Cable*/
|
||||
TLG_SIG_SRC_SVIDEO = 0x04,/* Signal src is: S_VIDEO */
|
||||
TLG_SIG_SRC_COMPOSITE = 0x08 /* Signal src is: Composite Video */
|
||||
};
|
||||
|
||||
enum tuner_analog_video_standard {
|
||||
TLG_TUNE_VSTD_NONE = 0x00000000,
|
||||
TLG_TUNE_VSTD_NTSC_M = 0x00000001,
|
||||
TLG_TUNE_VSTD_NTSC_M_J = 0x00000002,/* Japan */
|
||||
TLG_TUNE_VSTD_PAL_B = 0x00000010,
|
||||
TLG_TUNE_VSTD_PAL_D = 0x00000020,
|
||||
TLG_TUNE_VSTD_PAL_G = 0x00000040,
|
||||
TLG_TUNE_VSTD_PAL_H = 0x00000080,
|
||||
TLG_TUNE_VSTD_PAL_I = 0x00000100,
|
||||
TLG_TUNE_VSTD_PAL_M = 0x00000200,
|
||||
TLG_TUNE_VSTD_PAL_N = 0x00000400,
|
||||
TLG_TUNE_VSTD_SECAM_B = 0x00001000,
|
||||
TLG_TUNE_VSTD_SECAM_D = 0x00002000,
|
||||
TLG_TUNE_VSTD_SECAM_G = 0x00004000,
|
||||
TLG_TUNE_VSTD_SECAM_H = 0x00008000,
|
||||
TLG_TUNE_VSTD_SECAM_K = 0x00010000,
|
||||
TLG_TUNE_VSTD_SECAM_K1 = 0x00020000,
|
||||
TLG_TUNE_VSTD_SECAM_L = 0x00040000,
|
||||
TLG_TUNE_VSTD_SECAM_L1 = 0x00080000,
|
||||
TLG_TUNE_VSTD_PAL_N_COMBO = 0x00100000
|
||||
};
|
||||
|
||||
enum tlg_mode_caps {
|
||||
TLG_MODE_CAPS_NONE = 0x00, /* No Mode specified */
|
||||
TLG_MODE_CAPS_ANALOG_TV_UNCOMP = 0x01, /* Analog TV mode */
|
||||
TLG_MODE_CAPS_ANALOG_TV_COMP = 0x02, /* Analog TV (compressed)*/
|
||||
TLG_MODE_CAPS_FM_RADIO = 0x04, /* FM Radio mode */
|
||||
TLG_MODE_CAPS_DVB_T = 0x08, /* Digital TV (DVB-T) */
|
||||
};
|
||||
|
||||
enum poseidon_vendor_cmds {
|
||||
LAST_CMD_STAT = 0x00,
|
||||
GET_CHIP_ID = 0x01,
|
||||
GET_FW_ID = 0x02,
|
||||
PRODUCT_CAPS = 0x03,
|
||||
|
||||
TUNE_MODE_CAP_ATV = 0x10,
|
||||
TUNE_MODE_CAP_ATVCOMP = 0X10,
|
||||
TUNE_MODE_CAP_DVBT = 0x10,
|
||||
TUNE_MODE_CAP_FM = 0x10,
|
||||
TUNE_MODE_SELECT = 0x11,
|
||||
TUNE_FREQ_SELECT = 0x12,
|
||||
SGNL_SRC_SEL = 0x13,
|
||||
|
||||
VIDEO_STD_SEL = 0x14,
|
||||
VIDEO_STREAM_FMT_SEL = 0x15,
|
||||
VIDEO_ROSOLU_AVAIL = 0x16,
|
||||
VIDEO_ROSOLU_SEL = 0x17,
|
||||
VIDEO_CONT_PROTECT = 0x20,
|
||||
|
||||
VCR_TIMING_MODSEL = 0x21,
|
||||
EXT_AUDIO_CAP = 0x22,
|
||||
EXT_AUDIO_SEL = 0x23,
|
||||
TEST_PATTERN_SEL = 0x24,
|
||||
VBI_DATA_SEL = 0x25,
|
||||
AUDIO_SAMPLE_RATE_CAP = 0x28,
|
||||
AUDIO_SAMPLE_RATE_SEL = 0x29,
|
||||
TUNER_AUD_MODE = 0x2a,
|
||||
TUNER_AUD_MODE_AVAIL = 0x2b,
|
||||
TUNER_AUD_ANA_STD = 0x2c,
|
||||
TUNER_CUSTOM_PARAMETER = 0x2f,
|
||||
|
||||
DVBT_TUNE_MODE_SEL = 0x30,
|
||||
DVBT_BANDW_CAP = 0x31,
|
||||
DVBT_BANDW_SEL = 0x32,
|
||||
DVBT_GUARD_INTERV_CAP = 0x33,
|
||||
DVBT_GUARD_INTERV_SEL = 0x34,
|
||||
DVBT_MODULATION_CAP = 0x35,
|
||||
DVBT_MODULATION_SEL = 0x36,
|
||||
DVBT_INNER_FEC_RATE_CAP = 0x37,
|
||||
DVBT_INNER_FEC_RATE_SEL = 0x38,
|
||||
DVBT_TRANS_MODE_CAP = 0x39,
|
||||
DVBT_TRANS_MODE_SEL = 0x3a,
|
||||
DVBT_SEARCH_RANG = 0x3c,
|
||||
|
||||
TUNER_SETUP_ANALOG = 0x40,
|
||||
TUNER_SETUP_DIGITAL = 0x41,
|
||||
TUNER_SETUP_FM_RADIO = 0x42,
|
||||
TAKE_REQUEST = 0x43, /* Take effect of the command */
|
||||
PLAY_SERVICE = 0x44, /* Play start or Play stop */
|
||||
TUNER_STATUS = 0x45,
|
||||
TUNE_PROP_DVBT = 0x46,
|
||||
ERR_RATE_STATS = 0x47,
|
||||
TUNER_BER_RATE = 0x48,
|
||||
|
||||
SCAN_CAPS = 0x50,
|
||||
SCAN_SETUP = 0x51,
|
||||
SCAN_SERVICE = 0x52,
|
||||
SCAN_STATS = 0x53,
|
||||
|
||||
PID_SET = 0x58,
|
||||
PID_UNSET = 0x59,
|
||||
PID_LIST = 0x5a,
|
||||
|
||||
IRD_CAP = 0x60,
|
||||
IRD_MODE_SEL = 0x61,
|
||||
IRD_SETUP = 0x62,
|
||||
|
||||
PTM_MODE_CAP = 0x70,
|
||||
PTM_MODE_SEL = 0x71,
|
||||
PTM_SERVICE = 0x72,
|
||||
TUNER_REG_SCRIPT = 0x73,
|
||||
CMD_CHIP_RST = 0x74,
|
||||
};
|
||||
|
||||
enum tlg_bw {
|
||||
TLG_BW_5 = 5,
|
||||
TLG_BW_6 = 6,
|
||||
TLG_BW_7 = 7,
|
||||
TLG_BW_8 = 8,
|
||||
TLG_BW_12 = 12,
|
||||
TLG_BW_15 = 15
|
||||
};
|
||||
|
||||
struct cmd_firmware_vers_s {
|
||||
uint8_t fw_rev_major;
|
||||
uint8_t fw_rev_minor;
|
||||
uint16_t fw_patch;
|
||||
};
|
||||
#endif /* VENDOR_CMD_H_ */
|
Loading…
Reference in New Issue