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:
Huang Shijie 2010-02-02 04:07:47 -03:00 committed by Mauro Carvalho Chehab
parent 433763faec
commit 5b3f03f044
13 changed files with 4280 additions and 0 deletions

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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/

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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;
}

View file

@ -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");

View file

@ -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

View file

@ -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_ */