Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
config VIDEO_TLG2300
tristate "Telegent TLG2300 USB video capture support"
depends on VIDEO_DEV && I2C && SND && DVB_CORE
select VIDEO_TUNER
select VIDEO_TVEEPROM
depends on RC_CORE
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
ccflags-y += -Idrivers/media/i2c
ccflags-y += -Idrivers/media/tuners
ccflags-y += -Idrivers/media/dvb-core
ccflags-y += -Idrivers/media/dvb-frontends

View File

@@ -0,0 +1,336 @@
#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/vmalloc.h>
#include <linux/proc_fs.h>
#include <linux/module.h>
#include <linux/gfp.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);
if (ret < 0) {
snd_card_free(card);
return ret;
}
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,271 @@
#ifndef PD_COMMON_H
#define PD_COMMON_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 <media/v4l2-ctrls.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_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 (45000000U)
#define TUNER_FREQ_MAX (862000000U)
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;
struct v4l2_ctrl_handler ctrl_handler;
/* 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;
unsigned int is_radio_streaming;
int pre_emphasis;
struct video_device fm_dev;
struct v4l2_ctrl_handler ctrl_handler;
};
#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 dtv_frontend_properties 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;
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 *);
/* 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);
/* 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);
extern int debug_mode;
void set_debug_mode(struct video_device *vfd, int debug_mode);
#ifdef CONFIG_PM
#define in_hibernation(pd) (pd->msg.event == PM_EVENT_FREEZE)
#else
#define in_hibernation(pd) (0)
#endif
#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)
#endif

View File

@@ -0,0 +1,597 @@
#include "pd-common.h"
#include <linux/kernel.h>
#include <linux/usb.h>
#include <linux/time.h>
#include <linux/dvb/dmx.h>
#include <linux/delay.h>
#include <linux/gfp.h>
#include "vendorcmds.h"
#include <linux/sched.h>
#include <linux/atomic.h>
static void dvb_urb_cleanup(struct pd_dvb_adapter *pd_dvb);
static int dvb_bandwidth[][2] = {
{ TLG_BW_8, 8000000 },
{ TLG_BW_7, 7000000 },
{ TLG_BW_6, 6000000 }
};
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;
}
#ifdef CONFIG_PM
static void poseidon_fe_release(struct dvb_frontend *fe)
{
struct poseidon *pd = fe->demodulator_priv;
pd->pm_suspend = NULL;
pd->pm_resume = NULL;
}
#else
#define poseidon_fe_release 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 dtv_frontend_properties *fep = &fe->dtv_property_cache;
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->bandwidth_hz == 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);
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 dtv_frontend_properties));
return 0;
}
static int poseidon_get_fe(struct dvb_frontend *fe)
{
struct dtv_frontend_properties *fep = &fe->dtv_property_cache;
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 = {
.delsys = { SYS_DVBT },
.info = {
.name = "Poseidon DVB-T",
.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,532 @@
/*
* 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/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 "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");
#define TLG2300_FIRMWARE "tlg2300_firmware.bin"
static const char *firmware_name = TLG2300_FIRMWARE;
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;
}
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 = kmemdup(fw->data, fwlength, GFP_KERNEL);
if (!fwbuf) {
ret = -ENOMEM;
goto out;
}
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;
}
static inline struct poseidon *get_pd(struct usb_interface *intf)
{
return usb_get_intfdata(intf);
}
#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) +
!list_empty(&pd->radio_data.fm_dev.fh_list);
}
/* 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 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 "TLG2300 auto suspend\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 "TLG2300 resume\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);
}
#else /* CONFIG_PM is not enabled: */
static inline struct poseidon *find_old_poseidon(struct usb_device *udev)
{
return NULL;
}
static inline void set_map_flags(struct poseidon *pd, struct usb_device *udev)
{
}
#endif
static int 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 0;
}
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) {
logpm(pd);
mutex_init(&pd->lock);
/* register v4l2 device */
ret = v4l2_device_register(&interface->dev, &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
pm_runtime_set_autosuspend_delay(&pd->udev->dev,
1000 * PM_SUSPEND_DELAY);
usb_enable_autosuspend(pd->udev);
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);
pd_dvb_usb_device_exit(pd);
poseidon_fm_exit(pd);
poseidon_audio_free(pd);
pd_video_exit(pd);
usb_set_intfdata(interface, NULL);
kref_put(&pd->kref, poseidon_delete);
}
static 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");
MODULE_VERSION("0.0.2");
MODULE_FIRMWARE(TLG2300_FIRMWARE);

View File

@@ -0,0 +1,340 @@
#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/mm.h>
#include <linux/mutex.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-event.h>
#include <media/v4l2-fh.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 76000000U
#define TUNER_FREQ_MAX_FM 108000000U
#define MAX_PREEMPHASIS (V4L2_PREEMPHASIS_75_uS + 1)
static int preemphasis[MAX_PREEMPHASIS] = {
TLG_TUNE_ASTD_NONE, /* V4L2_PREEMPHASIS_DISABLED */
TLG_TUNE_ASTD_FM_EUR, /* V4L2_PREEMPHASIS_50_uS */
TLG_TUNE_ASTD_FM_US, /* V4L2_PREEMPHASIS_75_uS */
};
static int poseidon_check_mode_radio(struct poseidon *p)
{
int ret;
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);
ret = send_set_req(p, TUNER_AUD_ANA_STD,
p->radio_data.pre_emphasis, &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 poseidon *p = video_drvdata(filp);
int ret = 0;
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;
}
ret = v4l2_fh_open(filp);
if (ret)
goto out;
usb_autopm_get_interface(p->interface);
if (0 == p->state) {
struct video_device *vfd = &p->radio_data.fm_dev;
/* default pre-emphasis */
if (p->radio_data.pre_emphasis == 0)
p->radio_data.pre_emphasis = TLG_TUNE_ASTD_FM_EUR;
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;
}
kref_get(&p->kref);
out:
mutex_unlock(&p->lock);
return ret;
}
static int poseidon_fm_close(struct file *filp)
{
struct poseidon *p = video_drvdata(filp);
struct radio_data *fm = &p->radio_data;
uint32_t status;
mutex_lock(&p->lock);
if (v4l2_fh_is_singular_file(filp))
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);
return v4l2_fh_release(filp);
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
struct poseidon *p = video_drvdata(file);
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->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
/* Report all capabilities of the USB device */
v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS |
V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE |
V4L2_CAP_AUDIO | V4L2_CAP_STREAMING |
V4L2_CAP_READWRITE;
return 0;
}
static const struct v4l2_file_operations poseidon_fm_fops = {
.owner = THIS_MODULE,
.open = poseidon_fm_open,
.release = poseidon_fm_close,
.poll = v4l2_ctrl_poll,
.unlocked_ioctl = video_ioctl2,
};
static int tlg_fm_vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *vt)
{
struct poseidon *p = video_drvdata(file);
struct tuner_fm_sig_stat_s fm_stat = {};
int ret, status, count = 5;
if (vt->index != 0)
return -EINVAL;
vt->type = V4L2_TUNER_RADIO;
vt->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW;
vt->rangelow = TUNER_FREQ_MIN_FM * 2 / 125;
vt->rangehigh = TUNER_FREQ_MAX_FM * 2 / 125;
vt->rxsubchans = V4L2_TUNER_SUB_STEREO;
vt->audmode = V4L2_TUNER_MODE_STEREO;
vt->signal = 0;
vt->afc = 0;
strlcpy(vt->name, "Radio", sizeof(vt->name));
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;
}
static int fm_get_freq(struct file *file, void *priv,
struct v4l2_frequency *argp)
{
struct poseidon *p = video_drvdata(file);
if (argp->tuner)
return -EINVAL;
argp->frequency = p->radio_data.fm_freq;
return 0;
}
static int set_frequency(struct poseidon *p, __u32 frequency)
{
__u32 freq ;
int ret, status;
mutex_lock(&p->lock);
ret = send_set_req(p, TUNER_AUD_ANA_STD,
p->radio_data.pre_emphasis, &status);
freq = (frequency * 125) / 2; /* Hz */
freq = clamp(freq, TUNER_FREQ_MIN_FM, TUNER_FREQ_MAX_FM);
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 = freq * 2 / 125;
error:
mutex_unlock(&p->lock);
return ret;
}
static int fm_set_freq(struct file *file, void *priv,
const struct v4l2_frequency *argp)
{
struct poseidon *p = video_drvdata(file);
if (argp->tuner)
return -EINVAL;
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);
}
static int tlg_fm_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct poseidon *p = container_of(ctrl->handler, struct poseidon,
radio_data.ctrl_handler);
int pre_emphasis;
u32 status;
switch (ctrl->id) {
case V4L2_CID_TUNE_PREEMPHASIS:
pre_emphasis = preemphasis[ctrl->val];
send_set_req(p, TUNER_AUD_ANA_STD, pre_emphasis, &status);
p->radio_data.pre_emphasis = pre_emphasis;
return 0;
}
return -EINVAL;
}
static int vidioc_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *vt)
{
return vt->index > 0 ? -EINVAL : 0;
}
static const struct v4l2_ctrl_ops tlg_fm_ctrl_ops = {
.s_ctrl = tlg_fm_s_ctrl,
};
static const struct v4l2_ioctl_ops poseidon_fm_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.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,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static struct video_device poseidon_fm_template = {
.name = "Telegent-Radio",
.fops = &poseidon_fm_fops,
.minor = -1,
.release = video_device_release_empty,
.ioctl_ops = &poseidon_fm_ioctl_ops,
};
int poseidon_fm_init(struct poseidon *p)
{
struct video_device *vfd = &p->radio_data.fm_dev;
struct v4l2_ctrl_handler *hdl = &p->radio_data.ctrl_handler;
*vfd = poseidon_fm_template;
set_frequency(p, TUNER_FREQ_MIN_FM);
v4l2_ctrl_handler_init(hdl, 1);
v4l2_ctrl_new_std_menu(hdl, &tlg_fm_ctrl_ops, V4L2_CID_TUNE_PREEMPHASIS,
V4L2_PREEMPHASIS_75_uS, 0, V4L2_PREEMPHASIS_50_uS);
if (hdl->error) {
v4l2_ctrl_handler_free(hdl);
return hdl->error;
}
vfd->v4l2_dev = &p->v4l2_dev;
vfd->ctrl_handler = hdl;
set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
video_set_drvdata(vfd, p);
return video_register_device(vfd, VFL_TYPE_RADIO, -1);
}
int poseidon_fm_exit(struct poseidon *p)
{
video_unregister_device(&p->radio_data.fm_dev);
v4l2_ctrl_handler_free(&p->radio_data.ctrl_handler);
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_ */