* [PATCH 00/11] add linux driver for chip TLG2300
@ 2009-11-20 3:24 Huang Shijie
2009-11-20 3:24 ` [PATCH 01/11] modify video's Kconfig and Makefile for tlg2300 Huang Shijie
2009-12-09 19:08 ` [PATCH 00/11] add linux driver for chip TLG2300 Mauro Carvalho Chehab
0 siblings, 2 replies; 21+ messages in thread
From: Huang Shijie @ 2009-11-20 3:24 UTC (permalink / raw)
To: mchehab; +Cc: linux-media, Huang Shijie
The TLG2300 is a chip of Telegent System.
It support analog tv,DVB-T and radio in a single chip.
The chip has been used in several dongles, such as aeromax DH-9000:
http://www.b2bdvb.com/dh-9000.htm
You can get more info from:
[1] http://www.telegent.com/
[2] http://www.telegent.com/press/2009Sept14_CSI.html
Huang Shijie (10):
add maitainers for tlg2300
add readme file for tlg2300
add Kconfig and Makefile for tlg2300
add header files for tlg2300
add the generic file
add video file for tlg2300
add vbi code for tlg2300
add audio support for tlg2300
add DVB-T support for tlg2300
add FM support for tlg2300
root (1):
modify video's Kconfig and Makefile for tlg2300
Documentation/video4linux/README.tlg2300 | 229 ++++
MAINTAINERS | 6 +
drivers/media/video/Kconfig | 2 +
drivers/media/video/Makefile | 1 +
drivers/media/video/tlg2300/Kconfig | 16 +
drivers/media/video/tlg2300/Makefile | 9 +
drivers/media/video/tlg2300/pd-alsa.c | 379 +++++++
drivers/media/video/tlg2300/pd-bufqueue.c | 185 ++++
drivers/media/video/tlg2300/pd-common.h | 318 ++++++
drivers/media/video/tlg2300/pd-dvb.c | 649 ++++++++++++
drivers/media/video/tlg2300/pd-main.c | 546 ++++++++++
drivers/media/video/tlg2300/pd-radio.c | 383 +++++++
drivers/media/video/tlg2300/pd-vbi.c | 183 ++++
drivers/media/video/tlg2300/pd-video.c | 1636 +++++++++++++++++++++++++++++
drivers/media/video/tlg2300/vendorcmds.h | 243 +++++
15 files changed, 4785 insertions(+), 0 deletions(-)
create mode 100644 Documentation/video4linux/README.tlg2300
create mode 100644 drivers/media/video/tlg2300/Kconfig
create mode 100644 drivers/media/video/tlg2300/Makefile
create mode 100644 drivers/media/video/tlg2300/pd-alsa.c
create mode 100644 drivers/media/video/tlg2300/pd-bufqueue.c
create mode 100644 drivers/media/video/tlg2300/pd-common.h
create mode 100644 drivers/media/video/tlg2300/pd-dvb.c
create mode 100644 drivers/media/video/tlg2300/pd-main.c
create mode 100644 drivers/media/video/tlg2300/pd-radio.c
create mode 100644 drivers/media/video/tlg2300/pd-vbi.c
create mode 100644 drivers/media/video/tlg2300/pd-video.c
create mode 100644 drivers/media/video/tlg2300/vendorcmds.h
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH 01/11] modify video's Kconfig and Makefile for tlg2300
2009-11-20 3:24 [PATCH 00/11] add linux driver for chip TLG2300 Huang Shijie
@ 2009-11-20 3:24 ` Huang Shijie
2009-11-20 3:24 ` [PATCH 02/11] add maitainers " Huang Shijie
2009-12-09 19:08 ` [PATCH 00/11] add linux driver for chip TLG2300 Mauro Carvalho Chehab
1 sibling, 1 reply; 21+ messages in thread
From: Huang Shijie @ 2009-11-20 3:24 UTC (permalink / raw)
To: mchehab; +Cc: linux-media, root, Huang Shijie
From: root <root@localhost.localdomain>
modify the two files for tlg2300.
Signed-off-by: Huang Shijie <shijie8@gmail.com>
---
drivers/media/video/Kconfig | 2 ++
drivers/media/video/Makefile | 1 +
2 files changed, 3 insertions(+), 0 deletions(-)
diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig
index dcf9fa9..52352d8 100644
--- a/drivers/media/video/Kconfig
+++ b/drivers/media/video/Kconfig
@@ -837,6 +837,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"
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile
index 9f2e321..f349ce4 100644
--- a/drivers/media/video/Makefile
+++ b/drivers/media/video/Makefile
@@ -95,6 +95,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/
--
1.6.0.6
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 02/11] add maitainers for tlg2300
2009-11-20 3:24 ` [PATCH 01/11] modify video's Kconfig and Makefile for tlg2300 Huang Shijie
@ 2009-11-20 3:24 ` Huang Shijie
[not found] ` <1258687493-4012-4-git-send-email-shijie8@gmail.com>
0 siblings, 1 reply; 21+ messages in thread
From: Huang Shijie @ 2009-11-20 3:24 UTC (permalink / raw)
To: mchehab; +Cc: linux-media, Huang Shijie
add maitainers for the driver.
Signed-off-by: Huang Shijie <shijie8@gmail.com>
---
MAINTAINERS | 6 ++++++
1 files changed, 6 insertions(+), 0 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 60299a9..a8f02d6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5673,6 +5673,12 @@ F: Documentation/networking/z8530drv.txt
F: drivers/net/hamradio/*scc.c
F: drivers/net/hamradio/z8530.h
+TLG2300 VIDEO DRIVER
+M: Huang Shijie <shijie8@gmail.com>
+M: Kang Yong <kongyong@telegent.com>
+M: Zhang Xiaobing<xbzhang@telegent.com>
+S: Supported
+
ZD1211RW WIRELESS DRIVER
M: Daniel Drake <dsd@gentoo.org>
M: Ulrich Kunitz <kune@deine-taler.de>
--
1.6.0.6
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 04/11] add Kconfig and Makefile for tlg2300
[not found] ` <1258687493-4012-4-git-send-email-shijie8@gmail.com>
@ 2009-11-20 3:24 ` Huang Shijie
2009-11-20 3:24 ` [PATCH 05/11] add header files " Huang Shijie
0 siblings, 1 reply; 21+ messages in thread
From: Huang Shijie @ 2009-11-20 3:24 UTC (permalink / raw)
To: mchehab; +Cc: linux-media, Huang Shijie
add Kconfig and Makefile for tlg2300.
Signed-off-by: Huang Shijie <shijie8@gmail.com>
---
drivers/media/video/tlg2300/Kconfig | 16 ++++++++++++++++
drivers/media/video/tlg2300/Makefile | 9 +++++++++
2 files changed, 25 insertions(+), 0 deletions(-)
create mode 100644 drivers/media/video/tlg2300/Kconfig
create mode 100644 drivers/media/video/tlg2300/Makefile
diff --git a/drivers/media/video/tlg2300/Kconfig b/drivers/media/video/tlg2300/Kconfig
new file mode 100644
index 0000000..2c29ec6
--- /dev/null
+++ b/drivers/media/video/tlg2300/Kconfig
@@ -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
diff --git a/drivers/media/video/tlg2300/Makefile b/drivers/media/video/tlg2300/Makefile
new file mode 100644
index 0000000..558c1ad
--- /dev/null
+++ b/drivers/media/video/tlg2300/Makefile
@@ -0,0 +1,9 @@
+poseidon-objs := pd-bufqueue.o pd-video.o pd-alsa.o pd-dvb.o pd-vbi.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
+
--
1.6.0.6
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 05/11] add header files for tlg2300
2009-11-20 3:24 ` [PATCH 04/11] add Kconfig and Makefile " Huang Shijie
@ 2009-11-20 3:24 ` Huang Shijie
2009-11-20 3:24 ` [PATCH 06/11] add the generic file Huang Shijie
0 siblings, 1 reply; 21+ messages in thread
From: Huang Shijie @ 2009-11-20 3:24 UTC (permalink / raw)
To: mchehab; +Cc: linux-media, Huang Shijie
pd-common.h contains the common data structures, while
vendorcmds.h contains the vendor commands for firmware.
Signed-off-by: Huang Shijie <shijie8@gmail.com>
---
drivers/media/video/tlg2300/pd-common.h | 318 ++++++++++++++++++++++++++++++
drivers/media/video/tlg2300/vendorcmds.h | 243 +++++++++++++++++++++++
2 files changed, 561 insertions(+), 0 deletions(-)
create mode 100644 drivers/media/video/tlg2300/pd-common.h
create mode 100644 drivers/media/video/tlg2300/vendorcmds.h
diff --git a/drivers/media/video/tlg2300/pd-common.h b/drivers/media/video/tlg2300/pd-common.h
new file mode 100644
index 0000000..e21091c
--- /dev/null
+++ b/drivers/media/video/tlg2300/pd-common.h
@@ -0,0 +1,318 @@
+#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/poll.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 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 POSEIDON_STATE_STREAM_CAP (0x4000)
+#define POSEIDON_STATE_IDLE_HIBERANTION (0x0200)
+
+#define PM_SUSPEND_DELAY 3
+
+extern struct list_head pd_device_list;
+
+#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)
+
+/* country code ioctl */
+#include <asm-generic/ioctl.h>
+#define PD_COUNTRY_CODE _IOW('V', BASE_VIDIOC_PRIVATE + 0, int)
+struct pd_frame {
+ int index;
+ int frame_seq;
+ struct list_head frame;
+ char *data;
+ int bytesused;
+
+ /* for user pointer*/
+ unsigned long userptr;
+ __u32 length;
+};
+
+enum mem_type {
+ USER_MEM,
+ KERNEL_MEM
+};
+
+struct pd_bufqueue {
+ spinlock_t queue_lock;
+ struct list_head inqueue;
+ struct list_head outqueue;
+ wait_queue_head_t queue_wq;
+ struct pd_frame *curr_frame;
+
+ unsigned int buf_count;
+ unsigned int buf_length;
+ struct pd_frame frame_buffer[MAX_BUFFER_NUM];
+
+ /* for user pointer */
+ u32 userptr_init;
+ struct page **user_pages[MAX_BUFFER_NUM];
+
+ enum mem_type mtype;
+ unsigned int is_reading;
+ unsigned int read_offset;
+ struct pd_frame *read_frame;
+};
+
+struct vbi_data {
+ struct video_device *vbi_dev;
+ struct video_data *video;
+
+ unsigned int vbi_frameSize;
+ unsigned int buf_count;
+ struct pd_bufqueue vbi_queue;
+
+ unsigned int copied;
+};
+
+struct pd_sbuf {
+ char *data;
+ struct urb *urb;
+};
+
+struct video_data {
+ /* frame buffer */
+ uint32_t frame_size;
+ uint32_t buf_count;
+
+ /* v4l2 video device */
+ struct video_device *video_dev;
+
+ /* current transfer mode ,bulk or iso */
+ int cur_transfer_mode;
+ int is_streaming;
+ u32 vbi_size;
+
+ u32 stream_method;
+#define STREAM_RW 0x0001
+#define STREAM_MMAP 0x0002
+#define STREAM_USER 0x0004
+
+ /* v4l2 parameters */
+ struct v4l2_format cur_format;
+
+ v4l2_std_id tvnormid;
+ u32 freq;
+ s32 audio_index;
+ s32 sig_index;
+
+ int users;
+ struct pd_bufqueue video_queue;
+
+ /* for simiple */
+ int field_count;
+
+ char *dst;
+ int lines_copied;
+ int prev_left;
+
+ int lines_per_field;
+ int lines_size;
+
+ struct usb_device *udev;
+ u8 endpoint_addr;
+ struct pd_sbuf pd_sbuf[SBUF_NUM];
+ struct vbi_data *vbi;
+};
+
+enum pcm_stream_state {
+ STREAM_OFF,
+ STREAM_INTERRUPT,
+ STREAM_ON,
+};
+
+#define AUDIO_BUFS (3)
+#define CAPTURE_STREAM_EN 1
+struct poseidon_audio {
+ struct urb *urb[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 usb_device *udev;
+ struct pd_sbuf dvb_sbuf[DVB_SBUF_NUM];
+ struct poseidon *pd_device;
+ u8 bulk_endAddre;
+ 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 poseidon {
+ struct list_head device_list;
+
+ /* device lock */
+ struct mutex lock;
+
+ /* Refrence counter */
+ struct kref kref;
+
+ /* hardware info */
+ struct usb_device *udev;
+ struct usb_interface *interface;
+
+ 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 work_struct work;
+
+#ifdef CONFIG_PM
+ int (*pm_suspend)(struct poseidon *);
+ int (*pm_resume)(struct poseidon *);
+ pm_message_t msg;
+
+ struct work_struct pm_work;
+ u8 portnum;
+
+ int (*pm_open)(struct file *);
+ struct inode *inode;
+#endif
+ struct file *file_for_stream; /* the active stream*/
+};
+
+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;
+ u32 Fsc;
+ u16 swidth, sheight; /* scaled standard width, height */
+ u16 totalwidth;
+ u8 adelay, bdelay, iform;
+ u8 vbipack;
+ u32 scaledtwidth;
+ u16 hdelayx1, hactivex1;
+ u16 vdelay;
+ u16 vtotal;
+ int sram;
+ u32 tlg_tvnorm;
+};
+
+/* video */
+int pd_video_init(struct poseidon *);
+void pd_video_exit(struct poseidon *);
+int pd_video_stop_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
+
+/* vbi */
+int vbi_init(struct poseidon *);
+int vbi_exit(struct poseidon *);
+int vbi_request_buf(struct vbi_data *, size_t);
+int vbi_release_buf(struct vbi_data *);
+
+/* 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 *);
+
+/* buffer queue */
+int pd_bufqueue_qbuf(struct pd_bufqueue* , int);
+int pd_bufqueue_dqbuf(struct pd_bufqueue*, unsigned int, struct pd_frame**);
+ssize_t pd_bufqueue_read(struct pd_bufqueue* , unsigned int ,
+ char __user *, size_t);
+void pd_bufqueue_init(struct pd_bufqueue*, unsigned int *,
+ unsigned int, enum mem_type);
+void pd_bufqueue_cleanup(struct pd_bufqueue *);
+void pd_bufqueue_wakeup(struct pd_bufqueue *);
+ssize_t pd_bufqueue_poll(struct pd_bufqueue* , struct file *, poll_table*);
+void reset_queue_stat(struct pd_bufqueue *);
+
+/* 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);
+
+/* misc */
+void poseidon_delete(struct kref *kref);
+#define in_hibernation(pd) (pd->msg.event == PM_EVENT_FREEZE)
+#define audio_in_hibernate(pd) (pd->audio.pm_state)
+#define log(a, ...) printk(KERN_DEBUG "[ %s : %.3d ] "a"\n", \
+ __func__, __LINE__, ## __VA_ARGS__)
+extern int country_code;
+extern int debug_mode;
+void set_debug_mode(struct video_device *vfd, int debug_mode);
+#endif
diff --git a/drivers/media/video/tlg2300/vendorcmds.h b/drivers/media/video/tlg2300/vendorcmds.h
new file mode 100644
index 0000000..ba6f4ae
--- /dev/null
+++ b/drivers/media/video/tlg2300/vendorcmds.h
@@ -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_ */
--
1.6.0.6
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 06/11] add the generic file
2009-11-20 3:24 ` [PATCH 05/11] add header files " Huang Shijie
@ 2009-11-20 3:24 ` Huang Shijie
2009-11-20 3:24 ` [PATCH 07/11] add video file for tlg2300 Huang Shijie
` (2 more replies)
0 siblings, 3 replies; 21+ messages in thread
From: Huang Shijie @ 2009-11-20 3:24 UTC (permalink / raw)
To: mchehab; +Cc: linux-media, Huang Shijie
pd-main.c contains the ->probe(), it privides the generic
functions for analog TV,DVB-T and radio.
Signed-off-by: Huang Shijie <shijie8@gmail.com>
---
drivers/media/video/tlg2300/pd-main.c | 546 +++++++++++++++++++++++++++++++++
1 files changed, 546 insertions(+), 0 deletions(-)
create mode 100644 drivers/media/video/tlg2300/pd-main.c
diff --git a/drivers/media/video/tlg2300/pd-main.c b/drivers/media/video/tlg2300/pd-main.c
new file mode 100644
index 0000000..f997e2d
--- /dev/null
+++ b/drivers/media/video/tlg2300/pd-main.c
@@ -0,0 +1,546 @@
+/*
+ * 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@hotmail.com>
+ *
+ * (c) 2009 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/usb.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;
+LIST_HEAD(pd_device_list); /*should add a lock*/
+
+/*
+ * 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;
+
+ rc = usb_lock_device_for_reset(udev, iface);
+ if (rc >= 0) {
+ usb_reset_device(udev);
+ usb_unlock_device(udev);
+ }
+ }
+ break;
+ case PM_HIBERNATION_PREPARE:
+ list_for_each_entry(pd, &pd_device_list, device_list) {
+ if (pd->interface->pm_usage_cnt <= 0) {
+ pd->state |= POSEIDON_STATE_IDLE_HIBERANTION;
+ usb_autopm_get_interface(pd->interface);
+ }
+ /* for audio */
+ if (pd->audio.users)
+ pd->audio.pm_state = 1;
+ }
+ break;
+ default:
+ log("event :%ld\n", event);
+ }
+ return 0;
+}
+
+static struct notifier_block pm_notifer = {
+ .notifier_call = pm_notifier_block,
+};
+
+int usb_softrst(struct poseidon *pd, s32 udelay)
+{
+ s32 ret = 0;
+ s32 cmd_status = 0;
+
+ if (pd->state & POSEIDON_STATE_DISCONNECT)
+ return -ENODEV;
+
+ ret = send_set_req(pd, CMD_CHIP_RST, udelay, &cmd_status);
+ if (ret || cmd_status)
+ return -ENXIO;
+ return ret;
+}
+
+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(&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;
+}
+
+/* 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;
+}
+
+/* fixup something for poseidon */
+static inline struct poseidon *fixup(struct poseidon *pd)
+{
+ list_del(&pd->device_list);
+
+ kref_get(&pd->kref);
+
+ /* old udev and interface have gone, so put back reference . */
+ usb_put_dev(pd->udev);
+ usb_put_intf(pd->interface);
+ usb_autopm_set_interface(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;
+}
+
+#ifdef CONFIG_PM
+/* Is the card working now ? */
+static inline int is_working(struct poseidon *pd)
+{
+ if (pd->state & POSEIDON_STATE_IDLE_HIBERANTION)
+ return 0;
+ return pd->interface->pm_usage_cnt > 0;
+}
+
+static int poseidon_suspend(struct usb_interface *intf, pm_message_t msg)
+{
+ struct poseidon *pd = usb_get_intfdata(intf);
+
+ if (!is_working(pd)) {
+ if (pd->interface->pm_usage_cnt <= 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");
+ }
+ return 0;
+ }
+ pd->msg = msg;
+ return pd->pm_suspend ? pd->pm_suspend(pd) : 0;
+}
+
+static int poseidon_resume(struct usb_interface *intf)
+{
+ struct poseidon *pd = usb_get_intfdata(intf);
+
+ printk(KERN_DEBUG "\n\t ++ TLG2300 resume ++\n");
+ if (!is_working(pd)) {
+ if (PM_EVENT_AUTO_SUSPEND == pd->msg.event)
+ pd->msg = PMSG_ON;
+ return 0;
+ }
+ 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);
+
+ log();
+ pd->msg.event = 0; /* clear it here */
+ pd->state &= ~POSEIDON_STATE_DISCONNECT;
+
+ /*
+ * we hibernated in the order : close() --> disconnect()
+ * so now ,we must do in reverse order : open() --> resume();
+ */
+ if (!pd->pm_open(pd->file_for_stream))
+ 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;
+
+ check_firmware(udev, &ret);
+ if (ret)
+ return 0;
+
+ 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);
+ }
+
+ pd->udev = usb_get_dev(udev);
+ pd->interface = usb_get_intf(interface);
+ pd->country_code = 86;
+ usb_set_intfdata(interface, pd);
+ mutex_init(&pd->lock);
+
+ /* register devices in /dev */
+ pd_video_init(pd);
+ poseidon_audio_init(pd);
+ vbi_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->autoresume_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 = usb_get_intfdata(interface);
+
+ if (!pd)
+ return;
+
+ mutex_lock(&pd->lock);
+ pd->state |= POSEIDON_STATE_DISCONNECT;
+ mutex_unlock(&pd->lock);
+
+ /* stop urb transferring */
+ pd_video_stop_stream(pd);
+ dvb_stop_streaming(&pd->dvb_data);
+
+ lock_kernel();
+ {
+ pd_dvb_usb_device_exit(pd);
+ poseidon_fm_exit(pd);
+
+ vbi_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)
+{
+ 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");
--
1.6.0.6
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 07/11] add video file for tlg2300
2009-11-20 3:24 ` [PATCH 06/11] add the generic file Huang Shijie
@ 2009-11-20 3:24 ` Huang Shijie
2009-11-20 3:24 ` [PATCH 08/11] add vbi code " Huang Shijie
2009-12-09 18:48 ` [PATCH 07/11] add video file " Mauro Carvalho Chehab
2009-11-23 3:25 ` [PATCH 06/11] add the generic file Huang Shijie
2009-12-09 18:12 ` Mauro Carvalho Chehab
2 siblings, 2 replies; 21+ messages in thread
From: Huang Shijie @ 2009-11-20 3:24 UTC (permalink / raw)
To: mchehab; +Cc: linux-media, Huang Shijie
pd-video.c and pd-bufqueue.c contain the code for V4L2 implementation.
The code support read,mmap and user pointer.
Signed-off-by: Huang Shijie <shijie8@gmail.com>
---
drivers/media/video/tlg2300/pd-bufqueue.c | 185 ++++
drivers/media/video/tlg2300/pd-video.c | 1636 +++++++++++++++++++++++++++++
2 files changed, 1821 insertions(+), 0 deletions(-)
create mode 100644 drivers/media/video/tlg2300/pd-bufqueue.c
create mode 100644 drivers/media/video/tlg2300/pd-video.c
diff --git a/drivers/media/video/tlg2300/pd-bufqueue.c b/drivers/media/video/tlg2300/pd-bufqueue.c
new file mode 100644
index 0000000..6106fbd
--- /dev/null
+++ b/drivers/media/video/tlg2300/pd-bufqueue.c
@@ -0,0 +1,185 @@
+#include "pd-common.h"
+#include <linux/sched.h>
+#include <linux/vmalloc.h>
+#include <linux/uaccess.h>
+#include <linux/poll.h>
+#include <linux/mm.h>
+
+void pd_bufqueue_wakeup(struct pd_bufqueue *queue)
+{
+ if (waitqueue_active(&queue->queue_wq))
+ wake_up_interruptible(&queue->queue_wq);
+}
+
+int pd_bufqueue_qbuf(struct pd_bufqueue *q, int index)
+{
+ unsigned long lock_flags;
+ struct pd_frame *f;
+
+ if (index < 0 || index >= q->buf_count)
+ return -EINVAL;
+ f = &q->frame_buffer[index];
+
+ spin_lock_irqsave(&q->queue_lock, lock_flags);
+ if (list_empty(&f->frame))
+ list_add_tail(&f->frame, &q->inqueue);
+ spin_unlock_irqrestore(&q->queue_lock, lock_flags);
+ return 0;
+}
+
+int pd_bufqueue_dqbuf(struct pd_bufqueue *q, unsigned int f_flags,
+ struct pd_frame **pd_frame)
+{
+ unsigned long lock_flags;
+ int retval = -EAGAIN;
+
+ if (list_empty(&q->outqueue)) {
+ if (f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ retval = wait_event_interruptible_timeout(q->queue_wq,
+ !(list_empty(&q->outqueue)), HZ/2);
+ if (retval < 0)
+ return retval;
+ else if (retval == 0)
+ return -EAGAIN;
+ }
+
+ spin_lock_irqsave(&q->queue_lock, lock_flags);
+ if (!list_empty(&q->outqueue)) {
+ *pd_frame = list_entry(q->outqueue.next,
+ struct pd_frame, frame);
+ list_del_init(&(*pd_frame)->frame);
+ retval = 0;
+ }
+ spin_unlock_irqrestore(&q->queue_lock, lock_flags);
+
+ return retval;
+}
+
+ssize_t pd_bufqueue_poll(struct pd_bufqueue *queue, struct file *file,
+ poll_table *wait)
+{
+ if (list_empty(&queue->outqueue))
+ poll_wait(file, &queue->queue_wq, wait);
+
+ if (!list_empty(&queue->outqueue))
+ return POLLIN|POLLRDNORM;
+ return 0;
+}
+
+ssize_t pd_bufqueue_read(struct pd_bufqueue *queue, unsigned int f_flags,
+ char __user *buf, size_t count)
+{
+ int rc, retval = 0, copy = 0;
+ struct pd_frame *pd_frame = NULL;
+ unsigned long retn ;
+
+ if (!queue->is_reading)
+ queue->is_reading = 1;
+
+ while (count > 0) {
+ if (!queue->read_frame) {
+ retval = pd_bufqueue_dqbuf(queue,
+ f_flags, &queue->read_frame);
+ if (retval < 0)
+ goto readout;
+ }
+ pd_frame = queue->read_frame;
+
+ rc = min(pd_frame->bytesused - queue->read_offset, count);
+ retn = copy_to_user(buf + copy,
+ pd_frame->data + queue->read_offset, rc);
+ if (retn > 0)
+ ;
+ count -= rc;
+ queue->read_offset += rc;
+ copy += rc;
+
+ if (queue->read_offset == pd_frame->bytesused) {
+ pd_bufqueue_qbuf(queue, pd_frame->index);
+ queue->read_frame = NULL;
+ queue->read_offset = 0;
+ }
+ }
+readout:
+ return copy ? copy : retval;
+}
+
+void pd_bufqueue_cleanup(struct pd_bufqueue *queue)
+{
+ unsigned int i;
+
+ for (i = 0; i < queue->buf_count; i++) {
+ if (queue->frame_buffer[i].data) {
+ if (KERNEL_MEM == queue->mtype)
+ kfree(queue->frame_buffer[i].data);
+ else
+ vfree(queue->frame_buffer[i].data);
+
+ queue->frame_buffer[i].data = NULL;
+ }
+ }
+}
+
+void pd_bufqueue_init(struct pd_bufqueue *queue, unsigned int *buf_count,
+ unsigned int buf_length, enum mem_type type)
+{
+ unsigned int i, count, alignedsize;
+
+ if (queue->frame_buffer[0].data) {
+ if (buf_length > queue->buf_length) {
+ pd_bufqueue_cleanup(queue);
+ } else {
+ *buf_count = queue->buf_count;
+ goto init_out;
+ }
+ }
+ memset(queue, 0, sizeof(*queue));
+ queue->buf_length = buf_length;
+
+ count = (*buf_count > MAX_BUFFER_NUM) ? MAX_BUFFER_NUM : *buf_count;
+ alignedsize = PAGE_ALIGN(buf_length);
+
+ for (i = 0; i < count; i++) {
+ void *mem;
+
+ if (KERNEL_MEM == type)
+ mem = kcalloc(1, alignedsize, GFP_KERNEL);
+ else
+ mem = vmalloc_user(alignedsize);
+
+ if (!mem)
+ break;
+ queue->frame_buffer[i].data = mem;
+ queue->frame_buffer[i].index = i;
+ INIT_LIST_HEAD(&queue->frame_buffer[i].frame);
+ }
+ queue->buf_count = *buf_count = i;
+ queue->mtype = type;
+
+init_out:
+ spin_lock_init(&queue->queue_lock);
+ INIT_LIST_HEAD(&queue->inqueue);
+ INIT_LIST_HEAD(&queue->outqueue);
+ init_waitqueue_head(&queue->queue_wq);
+}
+
+void reset_queue_stat(struct pd_bufqueue *q)
+{
+ unsigned long flags;
+ struct pd_frame *f = &q->frame_buffer[0];
+
+ spin_lock_irqsave(&(q->queue_lock), flags);
+ for (; f < &q->frame_buffer[MAX_BUFFER_NUM]; f++) {
+ INIT_LIST_HEAD(&f->frame);
+ f->index = f - &q->frame_buffer[0];
+ }
+ INIT_LIST_HEAD(&q->inqueue);
+ INIT_LIST_HEAD(&q->outqueue);
+ q->read_frame = NULL;
+ q->curr_frame = NULL;
+ q->read_offset = 0;
+
+ spin_unlock_irqrestore(&(q->queue_lock), flags);
+}
diff --git a/drivers/media/video/tlg2300/pd-video.c b/drivers/media/video/tlg2300/pd-video.c
new file mode 100644
index 0000000..f9f6e19
--- /dev/null
+++ b/drivers/media/video/tlg2300/pd-video.c
@@ -0,0 +1,1636 @@
+#include "pd-common.h"
+#include "vendorcmds.h"
+#include <media/v4l2-dev.h>
+#include <linux/fs.h>
+#include <linux/vmalloc.h>
+#include <linux/videodev2.h>
+#include <linux/usb.h>
+#include <linux/mm.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/sched.h>
+
+static int pd_video_release(struct file *file);
+static int pd_video_open(struct file *file);
+static int pm_video_open(struct file *file);
+static int __pd_video_release(struct file *file);
+static int pm_video_suspend(struct poseidon *pd);
+static int pm_video_resume(struct poseidon *pd);
+static void iso_bubble_handler(struct work_struct *w);
+
+int country_code = 86;
+module_param(country_code, int, 0644);
+MODULE_PARM_DESC(country_code, "country code (e.g China is 86)");
+
+int usb_transfer_mode;
+module_param(usb_transfer_mode, int, 0644);
+MODULE_PARM_DESC(usb_transfer_mode, "0 = bulk, 1 = iscchronous");
+
+/*
+ * Drop frames if the hardware is not power enough
+ * and the default is the normal mode.
+ */
+int drop_frame;
+module_param(drop_frame, int, 0644);
+MODULE_PARM_DESC(drop_frame, "0 = normal, 1 = drop frame");
+
+#define DROP_MODE (1)
+#define FRAME_MAX_SIZE (720*576*2)
+#define ISO_PKT_SIZE (3072)
+
+static const struct poseidon_format poseidon_formats[] = {
+ { "YUV 422", V4L2_PIX_FMT_YUYV, 16, 0},
+ { "RGB565", V4L2_PIX_FMT_RGB565, 16, 0},
+};
+static const unsigned int POSEIDON_FORMATS = ARRAY_SIZE(poseidon_formats);
+
+#define tv(id, _name, w, h, cmd) \
+ { \
+ .v4l2_id = id, \
+ .name = _name, \
+ .swidth = w, \
+ .sheight = h, \
+ .tlg_tvnorm = cmd, \
+ }
+
+static const struct poseidon_tvnorm poseidon_tvnorms[] = {
+ tv(V4L2_STD_PAL_D, "PAL-D", 720, 576, TLG_TUNE_VSTD_PAL_D),
+ tv(V4L2_STD_PAL_B, "PAL-B", 720, 576, TLG_TUNE_VSTD_PAL_B),
+ tv(V4L2_STD_PAL_G, "PAL-G", 720, 576, TLG_TUNE_VSTD_PAL_G),
+ tv(V4L2_STD_PAL_H, "PAL-H", 720, 576, TLG_TUNE_VSTD_PAL_H),
+ tv(V4L2_STD_PAL_I, "PAL-I", 720, 576, TLG_TUNE_VSTD_PAL_I),
+ tv(V4L2_STD_PAL_M, "PAL-M", 720, 480, TLG_TUNE_VSTD_PAL_M),
+ tv(V4L2_STD_PAL_N, "PAL-N", 720, 576, TLG_TUNE_VSTD_PAL_N_COMBO),
+ tv(V4L2_STD_PAL_Nc, "PAL-Nc", 720, 576, TLG_TUNE_VSTD_PAL_N_COMBO),
+ tv(V4L2_STD_NTSC_M, "NTSC-M", 720, 480, TLG_TUNE_VSTD_NTSC_M),
+ tv(V4L2_STD_NTSC_M_JP, "NTSC-JP", 720, 480, TLG_TUNE_VSTD_NTSC_M_J),
+ tv(V4L2_STD_SECAM_B, "SECAM-B", 720, 576, TLG_TUNE_VSTD_SECAM_B),
+ tv(V4L2_STD_SECAM_D, "SECAM-D", 720, 576, TLG_TUNE_VSTD_SECAM_D),
+ tv(V4L2_STD_SECAM_G, "SECAM-G", 720, 576, TLG_TUNE_VSTD_SECAM_G),
+ tv(V4L2_STD_SECAM_H, "SECAM-H", 720, 576, TLG_TUNE_VSTD_SECAM_H),
+ tv(V4L2_STD_SECAM_K, "SECAM-K", 720, 576, TLG_TUNE_VSTD_SECAM_K),
+ tv(V4L2_STD_SECAM_K1, "SECAM-K1", 720, 576, TLG_TUNE_VSTD_SECAM_K1),
+ tv(V4L2_STD_SECAM_L, "SECAM-L", 720, 576, TLG_TUNE_VSTD_SECAM_L),
+ tv(V4L2_STD_SECAM_LC, "SECAM-LC", 720, 576, TLG_TUNE_VSTD_SECAM_L1),
+};
+static const unsigned int POSEIDON_TVNORMS = ARRAY_SIZE(poseidon_tvnorms);
+
+#define PD_TVNORMS_SUPPORT (V4L2_STD_PAL_D | V4L2_STD_PAL_B | V4L2_STD_PAL_G \
+ | V4L2_STD_PAL_H | V4L2_STD_PAL_I | V4L2_STD_PAL_M \
+ | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_NTSC_M \
+ | V4L2_STD_NTSC_M_JP | V4L2_STD_SECAM_B | V4L2_STD_SECAM_D \
+ | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H | V4L2_STD_SECAM_K \
+ | V4L2_STD_SECAM_K1 | V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC)
+
+struct pd_audio_mode {
+ u32 tlg_audio_mode;
+ u32 v4l2_audio_sub;
+ u32 v4l2_audio_mode;
+};
+
+static const struct pd_audio_mode pd_audio_modes[] = {
+ { TLG_TUNE_TVAUDIO_MODE_MONO, V4L2_TUNER_SUB_MONO,
+ V4L2_TUNER_MODE_MONO },
+ { TLG_TUNE_TVAUDIO_MODE_STEREO, V4L2_TUNER_SUB_STEREO,
+ V4L2_TUNER_MODE_STEREO },
+ { TLG_TUNE_TVAUDIO_MODE_LANG_A, V4L2_TUNER_SUB_LANG1,
+ V4L2_TUNER_MODE_LANG1 },
+ { TLG_TUNE_TVAUDIO_MODE_LANG_B, V4L2_TUNER_SUB_LANG2,
+ V4L2_TUNER_MODE_LANG2 },
+ { TLG_TUNE_TVAUDIO_MODE_LANG_C, V4L2_TUNER_SUB_LANG1,
+ V4L2_TUNER_MODE_LANG1_LANG2 }
+};
+static const unsigned int POSEIDON_AUDIOMODS = ARRAY_SIZE(pd_audio_modes);
+
+struct pd_input {
+ char *name;
+ uint32_t tlg_src;
+};
+
+static const struct pd_input pd_inputs[] = {
+ { "TV Antenna", TLG_SIG_SRC_ANTENNA },
+ { "TV Cable", TLG_SIG_SRC_CABLE },
+ { "TV SVideo", TLG_SIG_SRC_SVIDEO },
+ { "TV Composite", TLG_SIG_SRC_COMPOSITE }
+};
+static const unsigned int POSEIDON_INPUTS = ARRAY_SIZE(pd_inputs);
+
+struct poseidon_control {
+ struct v4l2_queryctrl v4l2_ctrl;
+ enum cmd_custom_param_id vc_id;
+};
+
+static struct poseidon_control controls[] = {
+ {
+ { V4L2_CID_BRIGHTNESS, V4L2_CTRL_TYPE_INTEGER,
+ "brightness", 0, 10000, 1, 100, 0, },
+ CUST_PARM_ID_BRIGHTNESS_CTRL
+ },
+
+ {
+ { V4L2_CID_CONTRAST, V4L2_CTRL_TYPE_INTEGER,
+ "contrast", 0, 10000, 1, 100, 0, },
+ CUST_PARM_ID_CONTRAST_CTRL,
+ },
+
+ {
+ { V4L2_CID_HUE, V4L2_CTRL_TYPE_INTEGER,
+ "hue", 0, 10000, 1, 100, 0, },
+ CUST_PARM_ID_HUE_CTRL,
+ },
+
+ {
+ { V4L2_CID_SATURATION, V4L2_CTRL_TYPE_INTEGER,
+ "saturation", 0, 10000, 1, 100, 0, },
+ CUST_PARM_ID_SATURATION_CTRL,
+ },
+};
+
+static struct v4l2_format default_v4l2_format = {
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ .fmt.pix = {
+ .width = 720,
+ .height = 576,
+ .pixelformat = V4L2_PIX_FMT_YUYV,
+ .field = V4L2_FIELD_INTERLACED,
+ .bytesperline = 720 * 2,
+ .sizeimage = 720 * 576 * 2,
+ .colorspace = V4L2_COLORSPACE_SMPTE170M,
+ .priv = 0
+ }
+};
+
+static int vidioc_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ strcpy(cap->driver, "Telegent Driver");
+ strcpy(cap->card, "Telegent Poseidon");
+ strcpy(cap->bus_info, "USB Bus");
+ cap->version = 0;
+ cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER |
+ V4L2_CAP_AUDIO | V4L2_CAP_STREAMING |
+ V4L2_CAP_READWRITE;
+ return 0;
+}
+
+/*====================================================================*/
+static void init_copy(struct video_data *video, bool index)
+{
+ video->field_count = index;
+ video->lines_copied = 0;
+ video->prev_left = 0 ;
+ video->dst = (char *)video->video_queue.curr_frame->data
+ + index * video->lines_size;
+ video->vbi->copied = 0; /* set it here */
+}
+
+static bool get_frame(struct pd_bufqueue *q, int *need_init)
+{
+ if (q->curr_frame)
+ return true;
+
+ spin_lock(&q->queue_lock);
+ if (!list_empty(&q->inqueue)) {
+ q->curr_frame = list_entry(q->inqueue.next,
+ struct pd_frame, frame);
+ list_del_init(&q->curr_frame->frame);
+ if (need_init)
+ *need_init = 1;
+ }
+ spin_unlock(&q->queue_lock);
+
+ return !!q->curr_frame;
+
+}
+
+/* check if the video's buffer is ready */
+static bool get_video_frame(struct video_data *video)
+{
+ struct pd_bufqueue *q = &video->video_queue;
+ int need_init = 0;
+ bool ret = true;
+
+ ret = get_frame(q, &need_init);
+ if (need_init)
+ init_copy(video, 0);
+ return ret;
+}
+
+static void put_back_frame(struct pd_bufqueue *q)
+{
+
+ if (q->curr_frame == NULL)
+ return;
+
+ spin_lock(&q->queue_lock);
+ list_add_tail(&q->curr_frame->frame, &q->outqueue);
+ q->curr_frame = NULL;
+ spin_unlock(&q->queue_lock);
+
+ if (waitqueue_active(&q->queue_wq))
+ wake_up_interruptible(&q->queue_wq);
+}
+
+static void end_field(struct video_data *video)
+{
+ if (DROP_MODE == drop_frame) {
+ int i = 0;
+ char *buf = (char *)video->video_queue.curr_frame->data;
+ int size = video->lines_size;
+
+ for (; i < video->lines_per_field; i++, buf += size * 2)
+ memcpy(buf + size, buf, size);
+
+ put_back_frame(&video->video_queue);
+ return;
+ }
+
+ if (1 == video->field_count)
+ put_back_frame(&video->video_queue);
+ else
+ init_copy(video, 1);
+}
+
+static void copy_video_data(struct video_data *video, char *src,
+ unsigned int count)
+{
+#define copy_data(len) \
+ do { \
+ if (++video->lines_copied > video->lines_per_field) \
+ goto overflow; \
+ memcpy(video->dst, src, len);\
+ video->dst += len + video->lines_size; \
+ src += len; \
+ count -= len; \
+ } while (0)
+
+ while (count && count >= video->lines_size) {
+ if (video->prev_left) {
+ copy_data(video->prev_left);
+ video->prev_left = 0;
+ continue;
+ }
+ copy_data(video->lines_size);
+ }
+ if (count && count < video->lines_size) {
+ memcpy(video->dst, src, count);
+
+ video->prev_left = video->lines_size - count;
+ video->dst += count;
+ }
+ return;
+
+overflow:
+ end_field(video);
+}
+
+static void check_trailer(struct video_data *video, char *src, int count)
+{
+ struct vbi_data *vbi = video->vbi;
+ int offset; /* trailer's offset */
+ char *buf;
+
+ offset = (video->cur_format.fmt.pix.sizeimage / 2 + video->vbi_size)
+ - (vbi->copied + video->lines_size * video->lines_copied);
+ if (video->prev_left)
+ offset -= (video->lines_size - video->prev_left);
+
+ if (offset > count || offset <= 0)
+ goto short_package;
+
+ buf = src + offset;
+
+ /* trailer : (VFHS) + U32 + U32 + field_num */
+ if (!strncmp(buf, "VFHS", 4)) {
+ int field_num = *((u32 *)(buf + 12));
+
+ if ((field_num & 1) ^ video->field_count) {
+ init_copy(video, video->field_count);
+ return;
+ }
+ copy_video_data(video, src, offset);
+ }
+short_package:
+ end_field(video);
+}
+
+static inline void copy_vbi_data(struct vbi_data *vbi,
+ char *src, unsigned int count)
+{
+ if (get_frame(&vbi->vbi_queue, NULL)) {
+ char *buf = vbi->vbi_queue.curr_frame->data;
+
+ if (vbi->video->field_count)
+ buf += vbi->video->vbi_size;
+ memcpy(buf + vbi->copied, src, count);
+ }
+ vbi->copied += count;
+}
+
+/*
+ * Copy the normal data (VBI or VIDEO) without the trailer.
+ * VBI is not interlaced, while VIDEO is interlaced.
+ */
+static inline void copy_vbi_video_data(struct video_data *video,
+ char *src, unsigned int count)
+{
+ struct vbi_data *vbi = video->vbi;
+ unsigned int vbi_delta = video->vbi_size - vbi->copied;
+
+ if (vbi_delta >= count) {
+ copy_vbi_data(vbi, src, count);
+ } else {
+ if (vbi_delta) {
+ copy_vbi_data(vbi, src, vbi_delta);
+ put_back_frame(&vbi->vbi_queue);
+ }
+ copy_video_data(video, src + vbi_delta, count - vbi_delta);
+ }
+}
+
+static void urb_complete_bulk(struct urb *urb)
+{
+ struct video_data *video = urb->context;
+ char *src = (char *)urb->transfer_buffer;
+ int count = urb->actual_length;
+ int ret = 0;
+
+ if (!video->is_streaming || urb->status) {
+ if (urb->status == -EPROTO)
+ goto resend_it;
+ return;
+ }
+
+ if (!get_video_frame(video))
+ goto resend_it;
+
+ if (count == urb->transfer_buffer_length)
+ copy_vbi_video_data(video, src, count);
+ else
+ check_trailer(video, src, count);
+
+resend_it:
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret)
+ log(" submit failed: error %d", ret);
+}
+
+/************************* for ISO *********************/
+#define GET_SUCCESS (0)
+#define GET_TRAILER (1)
+#define GET_TOO_MUCH_BUBBLE (2)
+#define GET_NONE (3)
+static int get_chunk(int start, struct urb *urb,
+ int *head, int *tail, int *bubble_err)
+{
+ struct usb_iso_packet_descriptor *pkt = NULL;
+ int ret = GET_SUCCESS;
+
+ for (*head = *tail = -1; start < urb->number_of_packets; start++) {
+ pkt = &urb->iso_frame_desc[start];
+
+ /* handle the bubble of the Hub */
+ if (-EOVERFLOW == pkt->status) {
+ if (++*bubble_err > urb->number_of_packets / 3)
+ return GET_TOO_MUCH_BUBBLE;
+ continue;
+ }
+
+ /* This is the gap */
+ if (pkt->status || pkt->actual_length <= 0
+ || pkt->actual_length > ISO_PKT_SIZE) {
+ if (*head != -1)
+ break;
+ continue;
+ }
+
+ /* a good isochronous packet */
+ if (pkt->actual_length == ISO_PKT_SIZE) {
+ if (*head == -1)
+ *head = start;
+ *tail = start;
+ continue;
+ }
+
+ /* trailer is here */
+ if (pkt->actual_length < ISO_PKT_SIZE) {
+ if (*head == -1) {
+ *head = start;
+ *tail = start;
+ return GET_TRAILER;
+ }
+ break;
+ }
+ }
+
+ if (*head == -1 && *tail == -1)
+ ret = GET_NONE;
+ return ret;
+}
+
+/*
+ * |__|------|___|-----|_______|
+ * ^ ^
+ * | |
+ * gap gap
+ */
+static void urb_complete_iso(struct urb *urb)
+{
+ struct video_data *video = urb->context;
+ int bubble_err = 0, head = 0, tail = 0;
+ char *src = (char *)urb->transfer_buffer;
+ int ret = 0;
+
+ if (!video->is_streaming)
+ return;
+
+ do {
+ if (!get_video_frame(video))
+ goto out;
+
+ switch (get_chunk(head, urb, &head, &tail, &bubble_err)) {
+ case GET_SUCCESS:
+ copy_vbi_video_data(video, src + (head * ISO_PKT_SIZE),
+ (tail - head + 1) * ISO_PKT_SIZE);
+ break;
+ case GET_TRAILER:
+ check_trailer(video, src + (head * ISO_PKT_SIZE),
+ ISO_PKT_SIZE);
+ break;
+ case GET_NONE:
+ goto out;
+ case GET_TOO_MUCH_BUBBLE:
+ log("\t We got too much bubble");
+ goto out;
+ }
+ } while (head = tail + 1, head < urb->number_of_packets);
+
+out:
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret)
+ log("usb_submit_urb err : %d", ret);
+}
+/*============================= [ end ] =====================*/
+
+static int prepare_iso_urb(struct video_data *video)
+{
+ int i;
+
+ if (video->pd_sbuf[0].urb)
+ return 0;
+
+ for (i = 0; i < SBUF_NUM; i++) {
+ int j;
+ struct urb *urb;
+
+ urb = usb_alloc_urb(PK_PER_URB, GFP_KERNEL);
+ if (urb == NULL)
+ goto out;
+
+ video->pd_sbuf[i].urb = urb;
+ video->pd_sbuf[i].data = usb_buffer_alloc(video->udev,
+ ISO_PKT_SIZE * PK_PER_URB,
+ GFP_KERNEL,
+ &urb->transfer_dma);
+
+ urb->complete = urb_complete_iso; /* handler */
+ urb->dev = video->udev;
+ urb->context = video;
+ urb->pipe = usb_rcvisocpipe(video->udev,
+ video->endpoint_addr);
+ urb->interval = 1;
+ urb->number_of_packets = PK_PER_URB;
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = video->pd_sbuf[i].data;
+ urb->transfer_buffer_length = PK_PER_URB * ISO_PKT_SIZE;
+
+ for (j = 0; j < PK_PER_URB; j++) {
+ urb->iso_frame_desc[j].offset = ISO_PKT_SIZE * j;
+ urb->iso_frame_desc[j].length = ISO_PKT_SIZE;
+ }
+ }
+ return 0;
+out:
+ for (; i > 0; i--)
+ ;
+ return -ENOMEM;
+}
+
+static int prepare_bulk_urb(struct video_data *video)
+{
+ int i;
+ int sb_size = 0x2000 ;
+
+ if (video->pd_sbuf[0].urb)
+ return 0;
+
+ for (i = 0; i < SBUF_NUM; i++) {
+ struct urb *urb = usb_alloc_urb(0, GFP_KERNEL);
+
+ if (urb == NULL)
+ return -ENOMEM;
+ video->pd_sbuf[i].urb = urb;
+ video->pd_sbuf[i].data = usb_buffer_alloc(video->udev,
+ sb_size,
+ GFP_KERNEL,
+ &urb->transfer_dma);
+ usb_fill_bulk_urb(urb, video->udev,
+ usb_rcvbulkpipe(video->udev, video->endpoint_addr),
+ video->pd_sbuf[i].data,
+ sb_size,
+ urb_complete_bulk,
+ video);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ }
+ return 0;
+}
+
+int usb_transfer_start(struct video_data *video)
+{
+ int i, ret;
+
+ video->is_streaming = 1;
+ for (i = 0; i < SBUF_NUM; i++) {
+ ret = usb_submit_urb(video->pd_sbuf[i].urb, GFP_KERNEL);
+ if (ret)
+ log("(%d) failed: error %d", i, ret);
+ }
+ return ret;
+}
+
+void usb_transfer_cleanup(struct video_data *video)
+{
+ struct pd_sbuf *sbuf = &video->pd_sbuf[0];
+
+ for (; sbuf < &video->pd_sbuf[SBUF_NUM]; sbuf++) {
+ if (sbuf->urb) {
+ usb_buffer_free(video->udev,
+ sbuf->urb->transfer_buffer_length,
+ sbuf->data,
+ sbuf->urb->transfer_dma);
+ usb_free_urb(sbuf->urb);
+ sbuf->urb = NULL;
+ sbuf->data = NULL;
+ }
+ }
+}
+
+int usb_transfer_stop(struct video_data *video)
+{
+ if (video->is_streaming) {
+ int i;
+
+ video->is_streaming = 0;
+ for (i = 0; i < SBUF_NUM; ++i) {
+ if (video->pd_sbuf[i].urb)
+ usb_kill_urb(video->pd_sbuf[i].urb);
+ }
+ usb_transfer_cleanup(video);
+ }
+ return 0;
+}
+
+static void usb_transfer_init(struct poseidon *pd, u8 addr,
+ struct usb_device *udev)
+{
+ struct video_data *video = &pd->video_data;
+
+ video->endpoint_addr = addr;
+ video->udev = udev;
+ video->vbi = &pd->vbi_data;
+ video->vbi->video = video;
+}
+
+static int vidioc_enum_fmt_cap(struct file *file, void *fh,
+ struct v4l2_fmtdesc *f)
+{
+ if (POSEIDON_FORMATS <= f->index)
+ return -EINVAL;
+ f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ f->flags = 0; /* not compressed image */
+ strcpy(f->description, poseidon_formats[f->index].name);
+ f->pixelformat = poseidon_formats[f->index].fourcc;
+ return 0;
+}
+
+static int vidioc_g_fmt_cap(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct poseidon *pd = fh;
+
+ *f = pd->video_data.cur_format;
+ return 0;
+}
+
+static int vidioc_try_fmt_cap(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ return 0;
+}
+
+static int vidioc_s_fmt_cap(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct poseidon *pd = fh;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ s32 ret = 0, cmd_status = 0, vid_resol;
+
+ if (V4L2_BUF_TYPE_VIDEO_CAPTURE != f->type)
+ return -EINVAL;
+ if (file != pd->file_for_stream
+ && (pd->state & POSEIDON_STATE_STREAM_CAP))
+ return -EBUSY;
+
+ mutex_lock(&pd->lock);
+ if (!(pd->state & POSEIDON_STATE_STREAM_CAP)) {
+ pd->state |= POSEIDON_STATE_STREAM_CAP;
+ pd->file_for_stream = file;
+ }
+
+ if (file == pd->file_for_stream) {
+ if (pix->pixelformat == V4L2_PIX_FMT_RGB565) {
+ vid_resol = TLG_TUNER_VID_FORMAT_RGB_565;
+ } else {
+ pix->pixelformat = V4L2_PIX_FMT_YUYV;
+ vid_resol = TLG_TUNER_VID_FORMAT_YUV;
+ }
+ ret = send_set_req(pd, VIDEO_STREAM_FMT_SEL,
+ vid_resol, &cmd_status);
+
+ vid_resol = TLG_TUNE_VID_RES_720;
+ switch (pix->width) {
+ case 704:
+ vid_resol = TLG_TUNE_VID_RES_704;
+ break;
+ default:
+ pix->width = 720;
+ case 720:
+ break;
+ }
+ ret |= send_set_req(pd, VIDEO_ROSOLU_SEL,
+ vid_resol, &cmd_status);
+ if (ret || cmd_status) {
+ mutex_unlock(&pd->lock);
+ return -ENXIO;
+ }
+ }
+ mutex_unlock(&pd->lock);
+
+ pix->height = (pd->video_data.tvnormid & V4L2_STD_525_60) ? 480 : 576;
+ pix->bytesperline = pix->width * 2;
+ pix->sizeimage = pix->width * pix->height * 2;
+
+ pd->video_data.cur_format = *f;
+
+ pd->video_data.lines_per_field = pix->height / 2;
+ pd->video_data.lines_size = pix->width * 2;
+ return 0;
+}
+
+static int set_std(struct poseidon *pd, v4l2_std_id *norm)
+{
+ s32 i, ret = 0, cmd_status, param;
+
+ for (i = 0; i < POSEIDON_TVNORMS; i++) {
+ if (*norm & poseidon_tvnorms[i].v4l2_id) {
+ param = poseidon_tvnorms[i].tlg_tvnorm;
+ goto found;
+ }
+ }
+ return -EINVAL;
+found:
+ mutex_lock(&pd->lock);
+ ret = send_set_req(pd, VIDEO_STD_SEL, param, &cmd_status);
+ if (ret || cmd_status)
+ goto out;
+
+ pd->video_data.tvnormid = poseidon_tvnorms[i].v4l2_id;
+ if (pd->video_data.tvnormid & V4L2_STD_525_60)
+ pd->video_data.vbi_size = V4L_NTSC_VBI_FRAMESIZE / 2;
+ else
+ pd->video_data.vbi_size = V4L_PAL_VBI_FRAMESIZE / 2;
+out:
+ mutex_unlock(&pd->lock);
+ return ret;
+}
+
+int vidioc_s_std(struct file *file, void *fh, v4l2_std_id *norm)
+{
+ return set_std((struct poseidon *)fh, norm);
+}
+
+static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *in)
+{
+ if (in->index < 0 || in->index >= POSEIDON_INPUTS)
+ return -EINVAL;
+ strcpy(in->name, pd_inputs[in->index].name);
+ in->type = V4L2_INPUT_TYPE_TUNER;
+
+ /*
+ * the audio input index mixed with this video input,
+ * Poseidon only have one audio/video, set to "0"
+ */
+ in->audioset = 0;
+ in->tuner = 0;
+ in->std = PD_TVNORMS_SUPPORT;
+ in->status = 0;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *fh, unsigned int *i)
+{
+ struct poseidon *pd = fh;
+ *i = pd->video_data.sig_index;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *fh, unsigned int i)
+{
+ s32 ret, cmd_status;
+ struct poseidon *pd = fh;
+
+ if (i < 0 || i >= POSEIDON_INPUTS)
+ return -EINVAL;
+ ret = send_set_req(pd, SGNL_SRC_SEL,
+ pd_inputs[i].tlg_src, &cmd_status);
+ if (ret)
+ return ret;
+
+ pd->video_data.sig_index = i;
+ return 0;
+}
+
+static struct poseidon_control *check_control_id(__u32 id)
+{
+ struct poseidon_control *control = &controls[0];
+ int array_size = ARRAY_SIZE(controls);
+
+ for (; control < &controls[array_size]; control++)
+ if (control->v4l2_ctrl.id == id)
+ return control;
+ return NULL;
+}
+
+static int vidioc_queryctrl(struct file *file, void *fh,
+ struct v4l2_queryctrl *a)
+{
+ struct poseidon_control *control = NULL;
+
+ control = check_control_id(a->id);
+ if (!control)
+ return -EINVAL;
+
+ *a = control->v4l2_ctrl;
+ return 0;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *fh, struct v4l2_control *ctrl)
+{
+ struct poseidon *pd = fh;
+ struct poseidon_control *control = NULL;
+ s32 ret = 0, cmd_status;
+ struct tuner_custom_parameter_s tuner_param;
+
+ control = check_control_id(ctrl->id);
+ if (!control)
+ return -EINVAL;
+
+ mutex_lock(&pd->lock);
+ ret = send_get_req(pd, TUNER_CUSTOM_PARAMETER, control->vc_id,
+ &tuner_param, &cmd_status, sizeof(tuner_param));
+ mutex_unlock(&pd->lock);
+
+ if (ret || cmd_status)
+ return -1;
+
+ ctrl->value = tuner_param.param_value;
+ return 0;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *fh, struct v4l2_control *a)
+{
+ struct tuner_custom_parameter_s param = {};
+ struct poseidon_control *control = NULL;
+ s32 ret = 0, cmd_status, params;
+ struct poseidon *pd = fh;
+
+ control = check_control_id(a->id);
+ if (!control)
+ return -EINVAL;
+
+ param.param_value = a->value;
+ param.param_id = control->vc_id;
+ params = *(s32 *)¶m; /* temp code */
+
+ mutex_lock(&pd->lock);
+ ret = send_set_req(pd, TUNER_CUSTOM_PARAMETER, params, &cmd_status);
+ ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status);
+ mutex_unlock(&pd->lock);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(HZ/4);
+ return ret;
+}
+
+/* Audio ioctls */
+static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a)
+{
+ if (0 != a->index)
+ return -EINVAL;
+ a->capability = V4L2_AUDCAP_STEREO;
+ strcpy(a->name, "USB audio in");
+ /*Poseidon have no AVL function.*/
+ a->mode = 0;
+ return 0;
+}
+
+int vidioc_g_audio(struct file *file, void *fh, struct v4l2_audio *a)
+{
+ a->index = 0;
+ a->capability = V4L2_AUDCAP_STEREO;
+ strcpy(a->name, "USB audio in");
+ a->mode = 0;
+ return 0;
+}
+
+int vidioc_s_audio(struct file *file, void *fh, struct v4l2_audio *a)
+{
+ return (0 == a->index) ? 0 : -EINVAL;
+}
+
+/* Tuner ioctls */
+static int vidioc_g_tuner(struct file *file, void *fh, struct v4l2_tuner *tuner)
+{
+ s32 count = 5, ret, cmd_status;
+ int index;
+ struct tuner_atv_sig_stat_s atv_stat;
+ struct poseidon *pd = fh;
+
+ if (0 != tuner->index)
+ return -EINVAL;
+
+ mutex_lock(&pd->lock);
+ ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_ANALOG_TV,
+ &atv_stat, &cmd_status, sizeof(atv_stat));
+
+ while (atv_stat.sig_lock_busy && count-- && !ret) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(HZ);
+
+ ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_ANALOG_TV,
+ &atv_stat, &cmd_status, sizeof(atv_stat));
+ }
+ mutex_unlock(&pd->lock);
+
+ if (debug_mode)
+ log("P:%d,S:%d", atv_stat.sig_present, atv_stat.sig_strength);
+
+ if (ret || cmd_status)
+ tuner->signal = 0;
+ else if (atv_stat.sig_present && !atv_stat.sig_strength)
+ tuner->signal = 0xFFFF;
+ else
+ tuner->signal = (atv_stat.sig_strength * 255 / 10) << 8;
+
+ strcpy(tuner->name, "Telegent Systems");
+ tuner->type = V4L2_TUNER_ANALOG_TV;
+ tuner->rangelow = TUNER_FREQ_MIN / 62500;
+ tuner->rangehigh = TUNER_FREQ_MAX / 62500;
+ tuner->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO |
+ V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2;
+ index = pd->video_data.audio_index;
+ tuner->rxsubchans = pd_audio_modes[index].v4l2_audio_sub;
+ tuner->audmode = pd_audio_modes[index].v4l2_audio_mode;
+ tuner->afc = 0;
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *fh, struct v4l2_tuner *a)
+{
+ s32 index, ret = 0, cmd_status, param, audiomode;
+ struct poseidon *pd = fh;
+
+ if (0 != a->index)
+ return -EINVAL;
+
+ for (index = 0; index < POSEIDON_AUDIOMODS; index++)
+ if (a->audmode == pd_audio_modes[index].v4l2_audio_mode)
+ goto found;
+ return -EINVAL;
+found:
+ mutex_lock(&pd->lock);
+ param = pd_audio_modes[index].tlg_audio_mode;
+ ret = send_set_req(pd, TUNER_AUD_MODE, param, &cmd_status);
+ audiomode = get_audio_std(TLG_MODE_ANALOG_TV_UNCOMP, pd->country_code);
+ ret |= send_set_req(pd, TUNER_AUD_ANA_STD, audiomode,
+ &cmd_status);
+ if (!ret)
+ pd->video_data.audio_index = index;
+ mutex_unlock(&pd->lock);
+ return ret;
+}
+
+static int vidioc_g_frequency(struct file *file, void *fh,
+ struct v4l2_frequency *freq)
+{
+ struct poseidon *pd = fh;
+
+ if (0 != freq->tuner)
+ return -EINVAL;
+ freq->frequency = pd->video_data.freq;
+ freq->type = V4L2_TUNER_ANALOG_TV;
+ return 0;
+}
+
+static void userptr_queue_init(struct pd_bufqueue *q, int n)
+{
+ int i = 0;
+
+ for (i = 0; i < n; i++) {
+ INIT_LIST_HEAD(&q->frame_buffer[i].frame);
+ q->user_pages[i] = NULL;
+ }
+
+ spin_lock_init(&q->queue_lock);
+ q->buf_count = n;
+ q->userptr_init = 0;
+ INIT_LIST_HEAD(&q->inqueue);
+ INIT_LIST_HEAD(&q->outqueue);
+ init_waitqueue_head(&q->queue_wq);
+}
+
+static int pm_video_open(struct file *file)
+{
+ pd_video_open(file);
+ return 0;
+}
+
+static int set_frequency(struct poseidon *pd, __u32 frequency)
+{
+ s32 ret = 0, param, cmd_status;
+
+ param = frequency * 62500 / 1000;
+ if (param < TUNER_FREQ_MIN/1000 || param > TUNER_FREQ_MAX / 1000)
+ return -EINVAL;
+
+ mutex_lock(&pd->lock);
+ ret = send_set_req(pd, TUNE_FREQ_SELECT, param, &cmd_status);
+ ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status);
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(HZ / 4);
+ pd->video_data.freq = frequency;
+ mutex_unlock(&pd->lock);
+ return ret;
+}
+
+static int vidioc_s_frequency(struct file *file, void *fh,
+ struct v4l2_frequency *freq)
+{
+ struct poseidon *pd = fh;
+
+ pd->file_for_stream = file;
+#ifdef CONFIG_PM
+ pd->inode = file->f_dentry->d_inode;
+ pd->pm_suspend = pm_video_suspend;
+ pd->pm_resume = pm_video_resume;
+ pd->pm_open = pm_video_open;
+#endif
+ return set_frequency(pd, freq->frequency);
+}
+
+static int sanity_check(struct poseidon *pd, struct file *file,
+ struct v4l2_buffer *b)
+{
+ if (pd->file_for_stream != file)
+ return -EBUSY;
+
+ if (b->index > pd->video_data.video_queue.buf_count
+ || b->index < 0
+ || b->index >= MAX_BUFFER_NUM
+ || V4L2_BUF_TYPE_VIDEO_CAPTURE != b->type)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vidioc_reqbufs(struct file *file, void *fh,
+ struct v4l2_requestbuffers *b)
+{
+ u32 count = 0;
+ struct poseidon *pd = fh;
+
+ if (file != pd->file_for_stream)
+ return -EBUSY;
+ if (pd->video_data.stream_method)
+ return -EBUSY;
+
+ if (V4L2_MEMORY_USERPTR == b->memory) {
+ pd->video_data.buf_count = b->count;
+ pd->video_data.stream_method = STREAM_USER;
+ userptr_queue_init(&pd->video_data.video_queue, b->count);
+ } else /* if (V4L2_MEMORY_MMAP == b->memory) */ {
+ pd->video_data.stream_method = STREAM_MMAP;
+
+ count = b->count;
+ pd_bufqueue_init(&pd->video_data.video_queue, &count,
+ pd->video_data.frame_size, USER_MEM);
+ b->count = count;
+ pd->video_data.buf_count = count;
+ }
+ return 0;
+}
+
+static int vidioc_querybuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct poseidon *pd = fh;
+
+ if (pd->video_data.buf_count <= b->index)
+ return -EINVAL;
+
+ b->flags = V4L2_BUF_FLAG_MAPPED;
+ b->field = V4L2_FIELD_INTERLACED;
+ b->m.offset = b->index * pd->video_data.frame_size;
+ b->length = pd->video_data.frame_size;
+ return 0;
+}
+
+static void clear_user_pointer(struct pd_bufqueue *q)
+{
+ int i, j;
+
+ for (i = 0; i < q->buf_count; i++) {
+ if (!test_bit(i, (void *)&q->userptr_init))
+ continue;
+
+ vunmap(q->frame_buffer[i].data);
+ q->frame_buffer[i].data = NULL;
+
+ j = q->buf_length >> PAGE_SHIFT;
+ while (j)
+ put_page(q->user_pages[i][--j]);
+
+ kfree(q->user_pages[i]);
+ q->user_pages[i] = NULL;
+ }
+ q->userptr_init = 0;
+}
+
+static int setup_user_pointer(struct pd_bufqueue *q, struct v4l2_buffer *b)
+{
+ unsigned int nr_pages = b->length >> PAGE_SHIFT;
+ struct pd_frame *f = &q->frame_buffer[b->index];
+ int i;
+ int ret = -ENOMEM;
+ struct page **pagesp;
+
+ pagesp = kcalloc(1, sizeof(struct page *) * nr_pages, GFP_KERNEL);
+ if (!pagesp)
+ return -ENOMEM;
+
+ /* <1> The function get_user_pages_fast() will
+ * 1.) increase the page->count, and
+ * 2.) makes the Anonymous page Active.
+ * 3.) makes the pages pinned in memory.
+ * 4.) prevent the pages from migrating to other node in numa.
+ */
+ get_user_pages_fast((unsigned long)b->m.userptr, nr_pages, 1, pagesp);
+ if (ret < nr_pages)
+ goto free_part_pages;
+
+ /* <2> map the pages to VMALLOC space. */
+ f->data = vmap(pagesp, nr_pages, VM_MAP, PAGE_KERNEL);
+ if (!f->data)
+ goto free_all_pages;
+
+ /* <3> setup the driver's buffers */
+ f->userptr = b->m.userptr;
+ f->length = b->length;
+ f->index = b->index;
+
+ q->buf_length = b->length;
+ q->user_pages[b->index] = pagesp;
+ set_bit(b->index, (void *)&q->userptr_init);
+ return 0;
+
+free_all_pages:
+ ret = nr_pages;
+free_part_pages:
+ for (i = 0; i < ret; i++)
+ put_page(pagesp[i]);
+
+ kfree(pagesp);
+ return -EAGAIN;
+}
+
+static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct poseidon *pd = fh;
+ struct pd_bufqueue *q;
+ int ret = 0;
+
+ ret = sanity_check(pd, file, b);
+ if (ret)
+ return ret;
+
+ q = &pd->video_data.video_queue;
+
+ if (V4L2_MEMORY_USERPTR == b->memory) {
+ if (!test_bit(b->index, (void *)&q->userptr_init))
+ setup_user_pointer(q, b);
+ pd_bufqueue_qbuf(q, b->index);
+
+ b->flags |= V4L2_BUF_FLAG_QUEUED;
+ b->flags &= ~(V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_DONE);
+ ret = 0;
+ } else /*if (V4L2_MEMORY_MMAP == b->memory) */ {
+ ret = pd_bufqueue_qbuf(q, b->index);
+ if (!ret) {
+ b->flags |= V4L2_BUF_FLAG_QUEUED;
+ b->flags &= ~V4L2_BUF_FLAG_DONE;
+ }
+ }
+ return ret;
+}
+
+static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
+{
+ struct pd_frame *pd_frame = NULL;
+ struct poseidon *pd = fh;
+ struct video_data *video;
+ int ret;
+
+ ret = sanity_check(pd, file, b);
+ if (ret)
+ return ret;
+
+ video = &pd->video_data;
+ pd_bufqueue_dqbuf(&video->video_queue, file->f_flags, &pd_frame);
+ if (!pd_frame)
+ return -EAGAIN;
+
+ b->index = pd_frame->index;
+ b->sequence = pd_frame->frame_seq;
+ b->flags &= ~(V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE);
+ b->field = V4L2_FIELD_INTERLACED;
+ do_gettimeofday(&b->timestamp);
+
+ if (V4L2_MEMORY_USERPTR == b->memory) {
+ b->length = pd_frame->length;
+ b->m.userptr = pd_frame->userptr;
+ }
+ b->bytesused = video->cur_format.fmt.pix.sizeimage;
+
+ /* bgr to rgb */
+ if (video->cur_format.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) {
+ u16 *buf = (u16 *)pd_frame->data;
+ for (; buf < (u16 *)(pd_frame->data + b->bytesused); buf++)
+ *buf = ((*buf) & 0x7E0)
+ | (((*buf) << 11) & 0xF800)
+ | (((*buf) >> 11) & 0x1F);
+ }
+ return 0;
+}
+
+static int start_video_stream(struct poseidon *pd)
+{
+ s32 cmd_status;
+ struct video_data *video = &pd->video_data;
+
+ vbi_request_buf(&pd->vbi_data, pd->video_data.vbi_size * 2);
+ send_set_req(pd, TAKE_REQUEST, 0, &cmd_status);
+ send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_START, &cmd_status);
+
+ if (video->cur_transfer_mode) {
+ prepare_iso_urb(video);
+ INIT_WORK(&pd->work, iso_bubble_handler);
+ } else {
+ prepare_bulk_urb(video);
+ }
+ printk(KERN_DEBUG "\t\t The USB transfer mode is %s now.\n\n",
+ video->cur_transfer_mode ? "isochronous" : "bulk");
+
+ usb_transfer_start(&pd->video_data);
+ pd->video_data.is_streaming = 1;
+ return 0;
+}
+
+int stop_video_stream(struct poseidon *pd)
+{
+ if (pd->video_data.is_streaming) {
+ s32 cmd_status;
+
+ pd->video_data.is_streaming = 0;
+ pd_bufqueue_wakeup(&pd->video_data.video_queue);
+ usb_transfer_stop(&pd->video_data);
+
+ send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP,
+ &cmd_status);
+
+ reset_queue_stat(&pd->video_data.video_queue);
+ reset_queue_stat(&pd->vbi_data.vbi_queue);
+ }
+ return 0;
+}
+
+int pd_video_stop_stream(struct poseidon *pd)
+{
+ mutex_lock(&pd->lock);
+ stop_video_stream(pd);
+ mutex_unlock(&pd->lock);
+ return 0;
+}
+
+static void iso_bubble_handler(struct work_struct *w)
+{
+ struct poseidon *pd = container_of(w, struct poseidon, work);
+ unsigned int i;
+
+ mutex_lock(&pd->lock);
+ start_video_stream(pd);
+
+ /* refill the queue */
+ for (i = 0; i < pd->video_data.buf_count; i++)
+ pd_bufqueue_qbuf(&pd->video_data.video_queue, i);
+
+ stop_video_stream(pd);
+ mutex_unlock(&pd->lock);
+}
+
+static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type i)
+{
+ int ret = 0;
+ struct poseidon *pd = fh;
+
+ if (file != pd->file_for_stream)
+ return -EBUSY;
+
+ mutex_lock(&pd->lock);
+ ret = start_video_stream(pd);
+ mutex_unlock(&pd->lock);
+ return ret;
+}
+
+static int vidioc_streamoff(struct file *file, void *fh, enum v4l2_buf_type i)
+{
+ struct poseidon *pd = fh;
+
+ if (file != pd->file_for_stream)
+ return -EBUSY;
+
+ mutex_lock(&pd->lock);
+ stop_video_stream(pd);
+ mutex_unlock(&pd->lock);
+ return 0;
+}
+
+static int pd_video_checkmode(struct poseidon *pd)
+{
+ s32 ret = 0, cmd_status, audiomode;
+ int alternate;
+ struct video_data *video = &pd->video_data;
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(HZ/2);
+
+ if (video->cur_transfer_mode)
+ alternate = ISO_3K_BULK_ALTERNATE_IFACE;
+ else
+ alternate = BULK_ALTERNATE_IFACE;
+ ret = usb_set_interface(pd->udev, 0, alternate);
+ if (ret < 0)
+ goto error;
+
+ ret = set_tuner_mode(pd, TLG_MODE_ANALOG_TV);
+ ret |= send_set_req(pd, SGNL_SRC_SEL,
+ TLG_SIG_SRC_ANTENNA, &cmd_status);
+ ret |= send_set_req(pd, VIDEO_STD_SEL,
+ TLG_TUNE_VSTD_PAL_D, &cmd_status);
+ ret |= send_set_req(pd, VIDEO_STREAM_FMT_SEL,
+ TLG_TUNER_VID_FORMAT_YUV, &cmd_status);
+ ret |= send_set_req(pd, VIDEO_ROSOLU_SEL,
+ TLG_TUNE_VID_RES_720, &cmd_status);
+
+ /* drop-field mode */
+ if (DROP_MODE == drop_frame)
+ send_set_req(pd, 0x76, 0x100, &cmd_status);
+
+ ret |= send_set_req(pd, TUNE_FREQ_SELECT, TUNER_FREQ_MIN, &cmd_status);
+ ret |= send_set_req(pd, VBI_DATA_SEL, 1, &cmd_status);/* enable vbi */
+
+ audiomode = get_audio_std(TLG_MODE_ANALOG_TV_UNCOMP, pd->country_code);
+ ret |= send_set_req(pd, TUNER_AUD_ANA_STD, audiomode, &cmd_status);
+ ret |= send_set_req(pd, TUNER_AUD_MODE,
+ TLG_TUNE_TVAUDIO_MODE_STEREO, &cmd_status);
+ ret |= send_set_req(pd, AUDIO_SAMPLE_RATE_SEL,
+ ATV_AUDIO_RATE_48K, &cmd_status);
+
+ video->tvnormid = V4L2_STD_PAL_D;
+ video->cur_format = default_v4l2_format;
+ video->vbi_size = V4L_PAL_VBI_FRAMESIZE / 2;
+ video->audio_index = 1; /* STEREO */
+ video->sig_index = 0; /* Antenna */
+error:
+ return ret;
+}
+
+static int pm_video_suspend(struct poseidon *pd)
+{
+ pm_alsa_suspend(pd);
+
+ stop_video_stream(pd);
+ usb_transfer_cleanup(&pd->video_data);
+ usb_set_interface(pd->udev, 0, 0);
+ mdelay(2000);
+ return 0;
+}
+
+static int pm_video_resume(struct poseidon *pd)
+{
+ int i;
+ v4l2_std_id tvnorm_id;
+
+ if (in_hibernation(pd)) {
+ __pd_video_release(pd->file_for_stream);
+ return 0;
+ }
+
+ tvnorm_id = pd->video_data.tvnormid;
+ pd_video_checkmode(pd);
+ if (tvnorm_id != pd->video_data.tvnormid)
+ set_std(pd, &tvnorm_id);
+ vidioc_s_fmt_cap(pd->file_for_stream, pd, &pd->video_data.cur_format);
+ set_frequency(pd, pd->video_data.freq);
+
+ for (i = 0; i < pd->video_data.buf_count; i++)
+ pd_bufqueue_qbuf(&pd->video_data.video_queue, i);
+
+ start_video_stream(pd);
+ pm_alsa_resume(pd);
+ return 0;
+}
+
+void set_debug_mode(struct video_device *vfd, int debug_mode)
+{
+ vfd->debug = 0;
+ if (debug_mode == 1)
+ vfd->debug = V4L2_DEBUG_IOCTL;
+ if (debug_mode == 2)
+ vfd->debug = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG;
+}
+
+static int pd_video_open(struct file *file)
+{
+ s32 ret = 0;
+ struct video_device *vfd = video_devdata(file);
+ struct poseidon *pd = video_get_drvdata(vfd);
+
+ mutex_lock(&pd->lock);
+ if (pd->state & POSEIDON_STATE_DISCONNECT) {
+ ret = -ENODEV;
+ goto out;
+ }
+ if (pd->state && !(pd->state & POSEIDON_STATE_ANALOG)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ usb_autopm_get_interface(pd->interface);
+ if (!pd->state) {
+ /* handle custom parameter first */
+ pd->video_data.cur_transfer_mode = usb_transfer_mode;
+ pd->country_code = country_code;
+ set_debug_mode(vfd, debug_mode);
+
+ ret = pd_video_checkmode(pd);
+ if (ret < 0)
+ goto out;
+ pd->state |= POSEIDON_STATE_ANALOG;
+ pd->file_for_stream = file;
+ usb_transfer_init(pd, 0x82, pd->udev);
+ }
+ kref_get(&pd->kref);
+ file->private_data = pd;
+ pd->video_data.users++;
+out:
+ mutex_unlock(&pd->lock);
+ return ret;
+}
+
+static int __pd_video_release(struct file *file)
+{
+ struct poseidon *pd = file->private_data;
+
+ mutex_lock(&pd->lock);
+ if (file == pd->file_for_stream
+ && (pd->state & POSEIDON_STATE_STREAM_CAP)
+ && !in_hibernation(pd)) {
+ stop_video_stream(pd);
+ if (STREAM_USER == pd->video_data.stream_method)
+ clear_user_pointer(&pd->video_data.video_queue);
+ else
+ pd_bufqueue_cleanup(&pd->video_data.video_queue);
+
+ vbi_release_buf(&pd->vbi_data);
+ usb_transfer_cleanup(&pd->video_data);
+
+ pd->state &= ~POSEIDON_STATE_STREAM_CAP;
+ pd->video_data.stream_method = 0;
+ }
+
+ pd->video_data.users--;
+ if (0 == pd->video_data.users)
+ pd->state = 0;
+ mutex_unlock(&pd->lock);
+
+ kref_put(&pd->kref, poseidon_delete);
+ file->private_data = NULL;
+ return 0;
+}
+
+static int pd_video_release(struct file *file)
+{
+ struct poseidon *pd = file->private_data;
+
+ __pd_video_release(file);
+ usb_autopm_put_interface(pd->interface);
+ return 0;
+}
+
+static long pd_video_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int country_code;
+ struct poseidon *pd = file->private_data;
+ unsigned long ret;
+
+ if (!pd || pd->state & POSEIDON_STATE_DISCONNECT)
+ return -ENODEV;
+
+ switch (cmd) {
+ case PD_COUNTRY_CODE:
+ ret = copy_from_user(&country_code, (char __user *)arg,
+ sizeof(country_code));
+ if (0 == ret)
+ pd->country_code = country_code;
+ return ret ? -EAGAIN : 0;
+
+ default:
+ return video_ioctl2(file, cmd, arg);
+ }
+}
+
+static int pd_video_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct poseidon *pd = file->private_data;
+ struct pd_frame *frame;
+ int index;
+ int ret;
+
+ if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED))
+ return -EINVAL;
+
+ index = (vma->vm_pgoff << PAGE_SHIFT) / pd->video_data.frame_size;
+ frame = &pd->video_data.video_queue.frame_buffer[index];
+
+ if (!frame->data)
+ return -EINVAL;
+
+ ret = remap_vmalloc_range(vma, frame->data, 0);
+ if (ret)
+ return ret;
+ vma->vm_flags |= VM_DONTEXPAND;
+ return 0;
+}
+
+unsigned int pd_video_poll(struct file *file, poll_table *table)
+{
+ struct poseidon *pd = file->private_data;
+ return pd_bufqueue_poll(&pd->video_data.video_queue, file, table);
+}
+
+ssize_t pd_video_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ int ret = -EINVAL;
+ struct poseidon *pd = file->private_data;
+ struct video_data *video = &pd->video_data;
+
+ if (video->stream_method == 0 && !video->is_streaming) {
+ int buf_count = 4, i;
+
+ pd_bufqueue_init(&video->video_queue, &buf_count,
+ video->frame_size, USER_MEM);
+ video->buf_count = buf_count;
+ for (i = 0; i < buf_count; i++)
+ pd_bufqueue_qbuf(&video->video_queue, i);
+
+ mutex_lock(&pd->lock);
+ start_video_stream(pd);
+ mutex_unlock(&pd->lock);
+
+ video->stream_method = STREAM_RW;
+ }
+ if (video->stream_method == STREAM_RW)
+ ret = pd_bufqueue_read(&video->video_queue, file->f_flags,
+ buffer, count);
+ return ret;
+}
+
+static const struct v4l2_file_operations pd_video_fops = {
+ .owner = THIS_MODULE,
+ .open = pd_video_open,
+ .release = pd_video_release,
+ .ioctl = pd_video_ioctl,
+ .mmap = pd_video_mmap,
+ .poll = pd_video_poll,
+ .read = pd_video_read,
+};
+
+static const struct v4l2_ioctl_ops pd_video_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_cap,
+
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_cap,
+ /* Buffer handlers */
+ .vidioc_reqbufs = vidioc_reqbufs,
+ .vidioc_querybuf = vidioc_querybuf,
+ .vidioc_qbuf = vidioc_qbuf,
+ .vidioc_dqbuf = vidioc_dqbuf,
+ /* Stream on/off */
+ .vidioc_streamon = vidioc_streamon,
+ .vidioc_streamoff = vidioc_streamoff,
+
+ .vidioc_s_std = vidioc_s_std,
+
+ .vidioc_enum_input = vidioc_enum_input,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+
+ /* Control handling */
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+
+ /* Audio ioctls */
+ .vidioc_enumaudio = vidioc_enumaudio,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+
+ /* Tuner ioctls */
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+};
+
+static struct video_device pd_video_template = {
+ .name = "poseidon_video",
+ .fops = &pd_video_fops,
+ .minor = -1,
+ .release = video_device_release,
+ .tvnorms = PD_TVNORMS_SUPPORT,
+ .ioctl_ops = &pd_video_ioctl_ops,
+};
+
+struct video_device *vdev_init(struct poseidon *pd, struct video_device *tmp)
+{
+ struct video_device *vfd;
+
+ vfd = video_device_alloc();
+ if (vfd == NULL)
+ return NULL;
+ *vfd = *tmp;
+ vfd->minor = -1;
+ vfd->parent = &(pd->udev->dev);
+ vfd->release = video_device_release;
+ video_set_drvdata(vfd, pd);
+ return vfd;
+}
+
+int pd_video_init(struct poseidon *pd)
+{
+ struct video_data *video = &pd->video_data;
+ int ret;
+
+ video->frame_size = PAGE_ALIGN(FRAME_MAX_SIZE);
+ video->video_dev = vdev_init(pd, &pd_video_template);
+ if (video->video_dev == NULL)
+ return -ENOMEM;
+
+ ret = video_register_device(video->video_dev, VFL_TYPE_GRABBER, -1);
+ if (ret < 0) {
+ video_device_release(video->video_dev);
+ video->video_dev = NULL;
+ return ret;
+ }
+ return 0;
+}
+
+void pd_video_exit(struct poseidon *pd)
+{
+ struct video_data *video = &pd->video_data;
+
+ if (video->video_dev && (-1 != video->video_dev->minor)) {
+ video_unregister_device(video->video_dev);
+ video->video_dev = NULL;
+ }
+}
--
1.6.0.6
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 08/11] add vbi code for tlg2300
2009-11-20 3:24 ` [PATCH 07/11] add video file for tlg2300 Huang Shijie
@ 2009-11-20 3:24 ` Huang Shijie
2009-11-20 3:24 ` [PATCH 09/11] add audio support " Huang Shijie
2009-12-09 18:51 ` [PATCH 08/11] add vbi code " Mauro Carvalho Chehab
2009-12-09 18:48 ` [PATCH 07/11] add video file " Mauro Carvalho Chehab
1 sibling, 2 replies; 21+ messages in thread
From: Huang Shijie @ 2009-11-20 3:24 UTC (permalink / raw)
To: mchehab; +Cc: linux-media, Huang Shijie
This is the vbi module for tlg2300.
Signed-off-by: Huang Shijie <shijie8@gmail.com>
---
drivers/media/video/tlg2300/pd-vbi.c | 183 ++++++++++++++++++++++++++++++++++
1 files changed, 183 insertions(+), 0 deletions(-)
create mode 100644 drivers/media/video/tlg2300/pd-vbi.c
diff --git a/drivers/media/video/tlg2300/pd-vbi.c b/drivers/media/video/tlg2300/pd-vbi.c
new file mode 100644
index 0000000..fb9ee0d
--- /dev/null
+++ b/drivers/media/video/tlg2300/pd-vbi.c
@@ -0,0 +1,183 @@
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/version.h>
+#include <media/v4l2-common.h>
+#include <linux/types.h>
+#include <media/v4l2-dev.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/unistd.h>
+
+#include "vendorcmds.h"
+#include "pd-common.h"
+
+int vbi_request_buf(struct vbi_data *vbi_data, size_t size)
+{
+ int i, count = 4;
+
+ pd_bufqueue_init(&vbi_data->vbi_queue, &count, size, KERNEL_MEM);
+
+ for (i = 0; i < count; i++)
+ pd_bufqueue_qbuf(&vbi_data->vbi_queue, i);
+
+ vbi_data->buf_count = count;
+ return 0;
+}
+
+int vbi_release_buf(struct vbi_data *vbi_data)
+{
+ if (vbi_data->buf_count) {
+ pd_bufqueue_cleanup(&vbi_data->vbi_queue);
+ pd_bufqueue_wakeup(&vbi_data->vbi_queue);
+ vbi_data->buf_count = 0;
+ }
+ return 0;
+}
+
+static long vbi_v4l2_ioctl(struct file *file,
+ u32 cmd, unsigned long argp)
+{
+ struct poseidon *pd = file->private_data;
+ int ret = 0;
+
+ mutex_lock(&pd->lock);
+ switch (cmd) {
+ case VIDIOC_QUERYCAP: {
+ struct v4l2_capability *t_cap = (struct v4l2_capability *)argp;
+
+ strcpy(t_cap->driver, "Telegent Driver");
+ strcpy(t_cap->card, "Telegent Poseidon");
+ strcpy(t_cap->bus_info, "USB bus");
+ t_cap->version = 0;
+ t_cap->capabilities = V4L2_CAP_VBI_CAPTURE;
+ }
+ break;
+
+ case VIDIOC_S_FMT:
+ case VIDIOC_G_FMT: {
+ struct v4l2_format *v4l2_f = (struct v4l2_format *)argp;
+
+ v4l2_f->fmt.vbi.sampling_rate = 6750000 * 4;
+ v4l2_f->fmt.vbi.samples_per_line = 720*2/* VBI_LINE_LENGTH */;
+ v4l2_f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+ v4l2_f->fmt.vbi.offset = 64 * 4; /*FIXME: why offset */
+ if (pd->video_data.tvnormid & V4L2_STD_525_60) {
+ v4l2_f->fmt.vbi.start[0] = 10;
+ v4l2_f->fmt.vbi.start[1] = 264;
+ v4l2_f->fmt.vbi.count[0] = V4L_NTSC_VBI_LINES;
+ v4l2_f->fmt.vbi.count[1] = V4L_NTSC_VBI_LINES;
+ } else {
+ v4l2_f->fmt.vbi.start[0] = 6;
+ v4l2_f->fmt.vbi.start[1] = 314;
+ v4l2_f->fmt.vbi.count[0] = V4L_PAL_VBI_LINES;
+ v4l2_f->fmt.vbi.count[1] = V4L_PAL_VBI_LINES;
+ }
+ /* VBI_UNSYNC VBI_INTERLACED */
+ v4l2_f->fmt.vbi.flags = V4L2_VBI_UNSYNC;
+ }
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&pd->lock);
+ return ret;
+}
+
+static ssize_t vbi_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct poseidon *pd = file->private_data;
+ struct vbi_data *vbi = &pd->vbi_data;
+
+ return vbi->buf_count ?
+ pd_bufqueue_read(&vbi->vbi_queue, file->f_flags, buffer, count)
+ : -EINVAL;
+}
+
+static int vbi_open(struct file *file)
+{
+ int ret = 0;
+ struct video_device *vd = video_devdata(file);
+ struct poseidon *pd = video_get_drvdata(vd);
+
+ if (!pd)
+ return -ENODEV;
+
+ mutex_lock(&pd->lock);
+ if (pd->state & POSEIDON_STATE_DISCONNECT) {
+ ret = -ENODEV;
+ goto out;
+ }
+ kref_get(&pd->kref);
+ file->private_data = pd;
+ set_debug_mode(vd, debug_mode);
+out:
+ mutex_unlock(&pd->lock);
+ return ret;
+}
+
+static int vbi_release(struct file *file)
+{
+ struct poseidon *pd = file->private_data;
+
+ if (!pd)
+ return -ENODEV;
+
+ kref_put(&pd->kref, poseidon_delete);
+ return 0;
+}
+
+static const struct v4l2_file_operations vbi_file_ops = {
+ .owner = THIS_MODULE,
+ .open = vbi_open,
+ .release = vbi_release,
+ .read = vbi_read,
+ .ioctl = vbi_v4l2_ioctl,
+};
+
+static struct video_device vbi_device = {
+ .name = "poseidon_vbi",
+ .fops = &vbi_file_ops,
+ .release = video_device_release,
+};
+
+int vbi_init(struct poseidon *pd)
+{
+ int ret = 0;
+
+ memset(&(pd->vbi_data), 0, sizeof(struct vbi_data));
+
+ pd->vbi_data.vbi_dev = vdev_init(pd, &vbi_device);
+ if (pd->vbi_data.vbi_dev == NULL) {
+ ret = -ENOMEM;
+ goto vbi_error;
+ }
+
+ if (video_register_device(pd->vbi_data.vbi_dev, VFL_TYPE_VBI, -1) < 0) {
+ video_device_release(pd->vbi_data.vbi_dev);
+ pd->vbi_data.vbi_dev = NULL;
+ printk(KERN_DEBUG"vbi_init : video device register failed\n");
+ ret = -1;
+ }
+
+vbi_error:
+ return ret;
+}
+
+int vbi_exit(struct poseidon *pd)
+{
+ struct vbi_data *vbi_data = &pd->vbi_data;
+
+ if (vbi_data->vbi_dev) {
+ video_unregister_device(vbi_data->vbi_dev);
+ vbi_data->vbi_dev = NULL;
+ }
+ return 0;
+}
--
1.6.0.6
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 09/11] add audio support for tlg2300
2009-11-20 3:24 ` [PATCH 08/11] add vbi code " Huang Shijie
@ 2009-11-20 3:24 ` Huang Shijie
2009-11-20 3:24 ` [PATCH 10/11] add DVB-T " Huang Shijie
2009-12-09 18:52 ` [PATCH 09/11] add audio " Mauro Carvalho Chehab
2009-12-09 18:51 ` [PATCH 08/11] add vbi code " Mauro Carvalho Chehab
1 sibling, 2 replies; 21+ messages in thread
From: Huang Shijie @ 2009-11-20 3:24 UTC (permalink / raw)
To: mchehab; +Cc: linux-media, Huang Shijie
The module uses ALSA for the audio, it will register
a new card for tlg2300.
Signed-off-by: Huang Shijie <shijie8@gmail.com>
---
drivers/media/video/tlg2300/pd-alsa.c | 379 +++++++++++++++++++++++++++++++++
1 files changed, 379 insertions(+), 0 deletions(-)
create mode 100644 drivers/media/video/tlg2300/pd-alsa.c
diff --git a/drivers/media/video/tlg2300/pd-alsa.c b/drivers/media/video/tlg2300/pd-alsa.c
new file mode 100644
index 0000000..54a5aaa
--- /dev/null
+++ b/drivers/media/video/tlg2300/pd-alsa.c
@@ -0,0 +1,379 @@
+#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 ANOLOG_AUDIO_ID (0)
+#define FM_ID (1)
+#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;
+ kref_put(&p->kref, poseidon_delete);
+ usb_autopm_put_interface(p->interface);
+ 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++) {
+ struct urb *urb = pa->urb[i];
+
+ if (!urb)
+ continue;
+ usb_buffer_free(p->udev, urb->transfer_buffer_length,
+ urb->transfer_buffer, urb->transfer_dma);
+ usb_free_urb(urb);
+ pa->urb[i] = NULL;
+ }
+ return 0;
+}
+
+static int audio_buf_init(struct poseidon *p)
+{
+ int i, ret;
+ struct poseidon_audio *pa = &p->audio;
+
+ for (i = 0; i < AUDIO_BUFS; i++) {
+ struct urb *urb;
+ char *mem = NULL;
+
+ if (pa->urb[i])
+ continue;
+
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!urb) {
+ ret = -ENOMEM;
+ goto init_err;
+ }
+ mem = usb_buffer_alloc(p->udev, AUDIO_BUF_SIZE,
+ GFP_ATOMIC, &urb->transfer_dma);
+ if (!mem) {
+ usb_free_urb(urb);
+ goto init_err;
+ }
+ usb_fill_bulk_urb(urb, p->udev,
+ usb_rcvbulkpipe(p->udev, AUDIO_EP),
+ mem, AUDIO_BUF_SIZE,
+ complete_handler_audio,
+ pa);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ pa->urb[i] = urb;
+ }
+ return 0;
+
+init_err:
+ audio_buf_free(p);
+ return ret;
+}
+
+static int snd_pd_hw_capture_free(struct snd_pcm_substream *substream)
+{
+ struct poseidon *p = snd_pcm_substream_chip(substream);
+
+ 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_OFF)
+ return;
+
+ if (urb->status != 0) {
+ /*if (urb->status == -ESHUTDOWN)*/
+ return;
+ }
+
+ if (pa->capture_stream == STREAM_ON && substream && !urb->status) {
+ 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 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;
+ int i, ret;
+
+ if (debug_mode)
+ log("cmd %d\n", cmd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_START:
+ if (audio_in_hibernate(p))
+ return 0;
+ if (pa->capture_stream == STREAM_ON)
+ return 0;
+
+ pa->rcv_position = pa->copied_position = 0;
+ pa->capture_stream = STREAM_ON;
+
+ audio_buf_init(p);
+ for (i = 0; i < AUDIO_BUFS; i++) {
+ ret = usb_submit_urb(pa->urb[i], GFP_KERNEL);
+ if (ret)
+ log("urb err : %d", ret);
+ }
+ return 0;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ 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)
+{
+ struct poseidon_audio *pa = &p->audio;
+ int i;
+
+ snd_pcm_suspend(pa->capture_pcm_substream);
+
+ for (i = 0; i < AUDIO_BUFS; i++)
+ usb_kill_urb(pa->urb[i]);
+
+ audio_buf_free(p);
+ return 0;
+}
+
+int pm_alsa_resume(struct poseidon *p)
+{
+ struct poseidon_audio *pa = &p->audio;
+
+ if (audio_in_hibernate(p)) {
+ pa->pm_state = 0;
+ usb_autopm_get_interface(p->interface);
+ }
+ snd_pd_capture_trigger(pa->capture_pcm_substream,
+ SNDRV_PCM_TRIGGER_START);
+ 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;
+
+ if (audio_in_hibernate(p))
+ return 0;
+
+ ret = snd_card_create(-1, "poseidon_audio", 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 (audio_in_hibernate(p))
+ return 0;
+
+ if (pa->card)
+ snd_card_free(pa->card);
+ return 0;
+}
--
1.6.0.6
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 10/11] add DVB-T support for tlg2300
2009-11-20 3:24 ` [PATCH 09/11] add audio support " Huang Shijie
@ 2009-11-20 3:24 ` Huang Shijie
2009-11-20 3:24 ` [PATCH 11/11] add FM " Huang Shijie
2009-12-09 18:56 ` [PATCH 10/11] add DVB-T " Mauro Carvalho Chehab
2009-12-09 18:52 ` [PATCH 09/11] add audio " Mauro Carvalho Chehab
1 sibling, 2 replies; 21+ messages in thread
From: Huang Shijie @ 2009-11-20 3:24 UTC (permalink / raw)
To: mchehab; +Cc: linux-media, Huang Shijie
This module contains the code for DVB-T.
Signed-off-by: Huang Shijie <shijie8@gmail.com>
---
drivers/media/video/tlg2300/pd-dvb.c | 649 ++++++++++++++++++++++++++++++++++
1 files changed, 649 insertions(+), 0 deletions(-)
create mode 100644 drivers/media/video/tlg2300/pd-dvb.c
diff --git a/drivers/media/video/tlg2300/pd-dvb.c b/drivers/media/video/tlg2300/pd-dvb.c
new file mode 100644
index 0000000..933fff0
--- /dev/null
+++ b/drivers/media/video/tlg2300/pd-dvb.c
@@ -0,0 +1,649 @@
+#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)
+ 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 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);
+ mdelay(2000);
+ return 0;
+}
+
+
+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;
+}
+
+static int pm_dvb_resume(struct poseidon *pd)
+{
+ struct pd_dvb_adapter *pd_dvb = &pd->dvb_data;
+
+ if (in_hibernation(pd))
+ return 0;
+
+ /* recovery for hibernation */
+ if (pd->interface->pm_usage_cnt <= 0)
+ usb_autopm_get_interface(pd->interface);
+
+ poseidon_check_mode_dvbt(pd);
+ mdelay(1000);
+ poseidon_set_fe(&pd_dvb->dvb_fe, &pd_dvb->fe_param);
+
+ dvb_start_streaming(pd_dvb);
+ return 0;
+}
+
+static int pm_dvb_open(struct file *file) { return 0; }
+
+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;
+ pd->pm_open = pm_dvb_open;
+#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)
+{
+ struct pd_sbuf *sbuf = &pd_dvb->dvb_sbuf[0];
+ struct urb *urb;
+
+ if (sbuf->urb)
+ return 0;
+
+ for (; sbuf < &pd_dvb->dvb_sbuf[DVB_SBUF_NUM]; sbuf++) {
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (urb == NULL)
+ return -ENOMEM;
+ sbuf->urb = urb;
+ sbuf->data = usb_buffer_alloc(pd_dvb->udev,
+ DVB_URB_BUF_SIZE,
+ GFP_KERNEL,
+ &urb->transfer_dma);
+
+ usb_fill_bulk_urb(urb, pd_dvb->udev,
+ usb_rcvbulkpipe(pd_dvb->udev, pd_dvb->bulk_endAddre),
+ sbuf->data, DVB_URB_BUF_SIZE, dvb_urb_irq, pd_dvb);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ }
+ return 0;
+}
+
+static void dvb_urb_cleanup(struct pd_dvb_adapter *pd_dvb)
+{
+ struct pd_sbuf *sbuf = &pd_dvb->dvb_sbuf[0];
+
+ for (; sbuf < &pd_dvb->dvb_sbuf[DVB_SBUF_NUM]; sbuf++) {
+ if (sbuf->urb) {
+ usb_buffer_free(pd_dvb->udev,
+ sbuf->urb->transfer_buffer_length,
+ sbuf->data,
+ sbuf->urb->transfer_dma);
+ usb_free_urb(sbuf->urb);
+ sbuf->urb = NULL;
+ sbuf->data = NULL;
+ }
+ }
+}
+
+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->dvb_sbuf[i].urb,
+ 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->dvb_sbuf[i].urb)
+ usb_kill_urb(pd_dvb->dvb_sbuf[i].urb);
+
+ ret = send_set_req(pd, PLAY_SERVICE, 2, &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;
+}
+
+static void dvb_hibernate_set(struct pd_dvb_adapter *pd_dvb)
+{
+ pd_dvb->reserved[1] = 1;
+}
+static void dvb_hibernate_clear(struct pd_dvb_adapter *pd_dvb)
+{
+ pd_dvb->reserved[1] = 0;
+}
+static bool dvb_in_hibernate(struct pd_dvb_adapter *pd_dvb)
+{
+ return pd_dvb->reserved[1];
+}
+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;
+
+ if (dvb_in_hibernate(pd_dvb)) {
+ pd_dvb->udev = pd->udev;
+ dvb_hibernate_clear(pd_dvb);
+ return 0;
+ }
+
+ pd_dvb->bulk_endAddre = 0x82;
+ atomic_set(&pd_dvb->users, 0);
+ atomic_set(&pd_dvb->active_feed, 0);
+ pd_dvb->pd_device = pd;
+ pd_dvb->udev = pd->udev;
+
+ 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;
+
+ if (atomic_read(&pd_dvb->users) > 0 && in_hibernation(pd)) {
+ dvb_hibernate_set(pd_dvb);
+ return;
+ }
+
+ 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;
+}
--
1.6.0.6
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH 11/11] add FM support for tlg2300
2009-11-20 3:24 ` [PATCH 10/11] add DVB-T " Huang Shijie
@ 2009-11-20 3:24 ` Huang Shijie
2009-12-09 19:04 ` Mauro Carvalho Chehab
2009-12-09 18:56 ` [PATCH 10/11] add DVB-T " Mauro Carvalho Chehab
1 sibling, 1 reply; 21+ messages in thread
From: Huang Shijie @ 2009-11-20 3:24 UTC (permalink / raw)
To: mchehab; +Cc: linux-media, Huang Shijie
This module contains codes for radio.
The radio use the ALSA audio as input.
The mplayer should be compiled with --enable-radio and
--enable-radio-capture.
The command runs as this(assume the alsa audio registers to card 1):
#mplayer radio://103.7/capture/ -radio
adevice=hw=1,0:arate=48000 -rawaudio rate=48000:channels=2
Signed-off-by: Huang Shijie <shijie8@gmail.com>
---
drivers/media/video/tlg2300/pd-radio.c | 383 ++++++++++++++++++++++++++++++++
1 files changed, 383 insertions(+), 0 deletions(-)
create mode 100644 drivers/media/video/tlg2300/pd-radio.c
diff --git a/drivers/media/video/tlg2300/pd-radio.c b/drivers/media/video/tlg2300/pd-radio.c
new file mode 100644
index 0000000..2576f3a
--- /dev/null
+++ b/drivers/media/video/tlg2300/pd-radio.c
@@ -0,0 +1,383 @@
+#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);
+static int __poseidon_fm_close(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;
+}
+
+static int pm_fm_suspend(struct poseidon *p)
+{
+ pm_alsa_suspend(p);
+ usb_set_interface(p->udev, 0, 0);
+ mdelay(2000);
+ return 0;
+}
+
+static int pm_fm_resume(struct poseidon *p)
+{
+ if (in_hibernation(p)) {
+ __poseidon_fm_close(p->file_for_stream);
+ return 0;
+ }
+ poseidon_check_mode_radio(p);
+ set_frequency(p, p->radio_data.fm_freq);
+ pm_alsa_resume(p);
+ return 0;
+}
+
+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)
+ 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);
+ }
+ mutex_unlock(&p->lock);
+
+ kref_put(&p->kref, poseidon_delete);
+ filp->private_data = NULL;
+ return 0;
+}
+
+static int poseidon_fm_close(struct file *filp)
+{
+ struct poseidon *p = filp->private_data;
+
+ __poseidon_fm_close(filp);
+ usb_autopm_put_interface(p->interface);
+ return 0;
+}
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-tele", sizeof(v->driver));
+ strlcpy(v->card, "telegent Radio", sizeof(v->card));
+ sprintf(v->bus_info, "USB");
+ v->version = 0;
+ v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+ return 0;
+}
+
+static long pd_radio_ioctls(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct poseidon *p = file->private_data;
+ unsigned long ret;
+ int country_code;
+
+ if (in_hibernation(p))
+ return -1;
+
+ if (cmd == PD_COUNTRY_CODE) {
+ ret = copy_from_user(&country_code, (void __user *)arg,
+ sizeof(country_code));
+ if (0 == ret) {
+ p->country_code = country_code;
+ return 0;
+ } else
+ return -EAGAIN;
+ }
+ return video_ioctl2(file, cmd, arg);
+}
+
+static const struct v4l2_file_operations poseidon_fm_fops = {
+ .owner = THIS_MODULE,
+ .open = poseidon_fm_open,
+ .release = poseidon_fm_close,
+ .ioctl = pd_radio_ioctls,
+};
+
+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->inode = file->f_dentry->d_inode;
+ p->pm_open = poseidon_fm_open;
+ 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-FM",
+ .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)
+{
+ if (p->radio_data.fm_dev) {
+ video_unregister_device(p->radio_data.fm_dev);
+ p->radio_data.fm_dev = NULL;
+ }
+ return 0;
+}
--
1.6.0.6
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH 06/11] add the generic file
2009-11-20 3:24 ` [PATCH 06/11] add the generic file Huang Shijie
2009-11-20 3:24 ` [PATCH 07/11] add video file for tlg2300 Huang Shijie
@ 2009-11-23 3:25 ` Huang Shijie
2009-12-09 18:12 ` Mauro Carvalho Chehab
2 siblings, 0 replies; 21+ messages in thread
From: Huang Shijie @ 2009-11-23 3:25 UTC (permalink / raw)
Cc: mchehab, linux-media
> +#ifdef CONFIG_PM
> +/* Is the card working now ? */
> +static inline int is_working(struct poseidon *pd)
> +{
> + if (pd->state & POSEIDON_STATE_IDLE_HIBERANTION)
> + return 0;
> + return pd->interface->pm_usage_cnt > 0;
> +}
> +
> +static int poseidon_suspend(struct usb_interface *intf, pm_message_t msg)
> +{
> + struct poseidon *pd = usb_get_intfdata(intf);
> +
> + if (!is_working(pd)) {
> + if (pd->interface->pm_usage_cnt <= 0
>
`interface->pm_usage_cnt` has been changed to atomic_t type in the latest code.
> + && !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");
> + }
> + return 0;
> + }
> + pd->msg = msg;
>
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 06/11] add the generic file
2009-11-20 3:24 ` [PATCH 06/11] add the generic file Huang Shijie
2009-11-20 3:24 ` [PATCH 07/11] add video file for tlg2300 Huang Shijie
2009-11-23 3:25 ` [PATCH 06/11] add the generic file Huang Shijie
@ 2009-12-09 18:12 ` Mauro Carvalho Chehab
2 siblings, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2009-12-09 18:12 UTC (permalink / raw)
To: Huang Shijie; +Cc: linux-media
Sorry for not analysing this module earlier.
The way you ordered your patch series is weird and will break git bisect,
if committed on your order. So, I'm starting analizing with this patch.
I'll let the initial ones to the end.
You should send Makefile/Kconfig changes at the end of the patch series, to
avoid breaking compilation in the middle of your patch series.
Cheers,
Mauro.
Huang Shijie wrote:
> pd-main.c contains the ->probe(), it privides the generic
> functions for analog TV,DVB-T and radio.
>
> Signed-off-by: Huang Shijie <shijie8@gmail.com>
> ---
> drivers/media/video/tlg2300/pd-main.c | 546 +++++++++++++++++++++++++++++++++
> 1 files changed, 546 insertions(+), 0 deletions(-)
> create mode 100644 drivers/media/video/tlg2300/pd-main.c
>
> diff --git a/drivers/media/video/tlg2300/pd-main.c b/drivers/media/video/tlg2300/pd-main.c
> new file mode 100644
> index 0000000..f997e2d
> --- /dev/null
> +++ b/drivers/media/video/tlg2300/pd-main.c
> @@ -0,0 +1,546 @@
> +/*
> + * 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@hotmail.com>
> + *
> + * (c) 2009 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/usb.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;
> +LIST_HEAD(pd_device_list); /*should add a lock*/
> +
> +/*
> + * 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);
> + }
You're using host endian here, not little endian. If you want to use little endian,
you should be defining lower_16 and upper_16 as:
__le16 lower_16, upper_16;
And use the following macros to convert from cpu host endian from/into le16:
le16_to_cpu()
cpu_to_le16()
Otherwise, the driver will work only on little endian architectures.
The same comment applies to all other places where you need little endian.
It should be noticed, however, that usb_control_msg already take care of little
endian for you (drivers/usb/core/message.c):
dr->wValue = cpu_to_le16(value);
dr->wIndex = cpu_to_le16(index);
dr->wLength = cpu_to_le16(size);
So, in this specific case, just the comments are wrong.
> + 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;
> +
> + rc = usb_lock_device_for_reset(udev, iface);
> + if (rc >= 0) {
> + usb_reset_device(udev);
> + usb_unlock_device(udev);
> + }
> + }
> + break;
> + case PM_HIBERNATION_PREPARE:
> + list_for_each_entry(pd, &pd_device_list, device_list) {
> + if (pd->interface->pm_usage_cnt <= 0) {
> + pd->state |= POSEIDON_STATE_IDLE_HIBERANTION;
> + usb_autopm_get_interface(pd->interface);
> + }
> + /* for audio */
> + if (pd->audio.users)
> + pd->audio.pm_state = 1;
> + }
> + break;
> + default:
> + log("event :%ld\n", event);
> + }
> + return 0;
> +}
> +
> +static struct notifier_block pm_notifer = {
> + .notifier_call = pm_notifier_block,
> +};
> +
> +int usb_softrst(struct poseidon *pd, s32 udelay)
> +{
> + s32 ret = 0;
> + s32 cmd_status = 0;
> +
> + if (pd->state & POSEIDON_STATE_DISCONNECT)
> + return -ENODEV;
> +
> + ret = send_set_req(pd, CMD_CHIP_RST, udelay, &cmd_status);
> + if (ret || cmd_status)
> + return -ENXIO;
> + return ret;
> +}
> +
> +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(&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;
> +}
> +
> +/* 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;
> +}
> +
> +/* fixup something for poseidon */
> +static inline struct poseidon *fixup(struct poseidon *pd)
> +{
> + list_del(&pd->device_list);
> +
> + kref_get(&pd->kref);
> +
> + /* old udev and interface have gone, so put back reference . */
> + usb_put_dev(pd->udev);
> + usb_put_intf(pd->interface);
> + usb_autopm_set_interface(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;
> +}
> +
> +#ifdef CONFIG_PM
> +/* Is the card working now ? */
> +static inline int is_working(struct poseidon *pd)
> +{
> + if (pd->state & POSEIDON_STATE_IDLE_HIBERANTION)
> + return 0;
> + return pd->interface->pm_usage_cnt > 0;
> +}
> +
> +static int poseidon_suspend(struct usb_interface *intf, pm_message_t msg)
> +{
> + struct poseidon *pd = usb_get_intfdata(intf);
> +
> + if (!is_working(pd)) {
> + if (pd->interface->pm_usage_cnt <= 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");
> + }
> + return 0;
> + }
> + pd->msg = msg;
> + return pd->pm_suspend ? pd->pm_suspend(pd) : 0;
> +}
> +
> +static int poseidon_resume(struct usb_interface *intf)
> +{
> + struct poseidon *pd = usb_get_intfdata(intf);
> +
> + printk(KERN_DEBUG "\n\t ++ TLG2300 resume ++\n");
> + if (!is_working(pd)) {
> + if (PM_EVENT_AUTO_SUSPEND == pd->msg.event)
> + pd->msg = PMSG_ON;
> + return 0;
> + }
> + 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);
> +
> + log();
> + pd->msg.event = 0; /* clear it here */
> + pd->state &= ~POSEIDON_STATE_DISCONNECT;
> +
> + /*
> + * we hibernated in the order : close() --> disconnect()
> + * so now ,we must do in reverse order : open() --> resume();
> + */
> + if (!pd->pm_open(pd->file_for_stream))
> + 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;
> +
> + check_firmware(udev, &ret);
> + if (ret)
> + return 0;
> +
> + 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);
> + }
> +
> + pd->udev = usb_get_dev(udev);
> + pd->interface = usb_get_intf(interface);
> + pd->country_code = 86;
> + usb_set_intfdata(interface, pd);
> + mutex_init(&pd->lock);
> +
> + /* register devices in /dev */
> + pd_video_init(pd);
> + poseidon_audio_init(pd);
> + vbi_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->autoresume_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 = usb_get_intfdata(interface);
> +
> + if (!pd)
> + return;
> +
> + mutex_lock(&pd->lock);
> + pd->state |= POSEIDON_STATE_DISCONNECT;
> + mutex_unlock(&pd->lock);
> +
> + /* stop urb transferring */
> + pd_video_stop_stream(pd);
> + dvb_stop_streaming(&pd->dvb_data);
> +
> + lock_kernel();
Don't use KBL. It will be removed soon (likely on 2.6.34), so no new
drivers should use it anymore.
> + {
> + pd_dvb_usb_device_exit(pd);
> + poseidon_fm_exit(pd);
> +
> + vbi_exit(pd);
> + poseidon_audio_free(pd);
> + pd_video_exit(pd);
> + }
> + unlock_kernel();
Same here.
> +
> + 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)
> +{
> + 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");
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 07/11] add video file for tlg2300
2009-11-20 3:24 ` [PATCH 07/11] add video file for tlg2300 Huang Shijie
2009-11-20 3:24 ` [PATCH 08/11] add vbi code " Huang Shijie
@ 2009-12-09 18:48 ` Mauro Carvalho Chehab
1 sibling, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2009-12-09 18:48 UTC (permalink / raw)
To: Huang Shijie; +Cc: linux-media
Huang Shijie wrote:
> pd-video.c and pd-bufqueue.c contain the code for V4L2 implementation.
> The code support read,mmap and user pointer.
>
> Signed-off-by: Huang Shijie <shijie8@gmail.com>
> ---
> drivers/media/video/tlg2300/pd-bufqueue.c | 185 ++++
> drivers/media/video/tlg2300/pd-video.c | 1636 +++++++++++++++++++++++++++++
> 2 files changed, 1821 insertions(+), 0 deletions(-)
> create mode 100644 drivers/media/video/tlg2300/pd-bufqueue.c
> create mode 100644 drivers/media/video/tlg2300/pd-video.c
>
> diff --git a/drivers/media/video/tlg2300/pd-bufqueue.c b/drivers/media/video/tlg2300/pd-bufqueue.c
> new file mode 100644
> index 0000000..6106fbd
> --- /dev/null
> +++ b/drivers/media/video/tlg2300/pd-bufqueue.c
> @@ -0,0 +1,185 @@
> +#include "pd-common.h"
> +#include <linux/sched.h>
> +#include <linux/vmalloc.h>
> +#include <linux/uaccess.h>
> +#include <linux/poll.h>
> +#include <linux/mm.h>
> +
> +void pd_bufqueue_wakeup(struct pd_bufqueue *queue)
> +{
> + if (waitqueue_active(&queue->queue_wq))
> + wake_up_interruptible(&queue->queue_wq);
> +}
> +
> +int pd_bufqueue_qbuf(struct pd_bufqueue *q, int index)
> +{
> + unsigned long lock_flags;
> + struct pd_frame *f;
> +
> + if (index < 0 || index >= q->buf_count)
> + return -EINVAL;
> + f = &q->frame_buffer[index];
> +
> + spin_lock_irqsave(&q->queue_lock, lock_flags);
> + if (list_empty(&f->frame))
> + list_add_tail(&f->frame, &q->inqueue);
> + spin_unlock_irqrestore(&q->queue_lock, lock_flags);
> + return 0;
> +}
> +
> +int pd_bufqueue_dqbuf(struct pd_bufqueue *q, unsigned int f_flags,
> + struct pd_frame **pd_frame)
> +{
> + unsigned long lock_flags;
> + int retval = -EAGAIN;
> +
> + if (list_empty(&q->outqueue)) {
> + if (f_flags & O_NONBLOCK)
> + return -EAGAIN;
> +
> + retval = wait_event_interruptible_timeout(q->queue_wq,
> + !(list_empty(&q->outqueue)), HZ/2);
> + if (retval < 0)
> + return retval;
> + else if (retval == 0)
> + return -EAGAIN;
> + }
> +
> + spin_lock_irqsave(&q->queue_lock, lock_flags);
> + if (!list_empty(&q->outqueue)) {
> + *pd_frame = list_entry(q->outqueue.next,
> + struct pd_frame, frame);
> + list_del_init(&(*pd_frame)->frame);
> + retval = 0;
> + }
> + spin_unlock_irqrestore(&q->queue_lock, lock_flags);
> +
> + return retval;
> +}
> +
> +ssize_t pd_bufqueue_poll(struct pd_bufqueue *queue, struct file *file,
> + poll_table *wait)
> +{
> + if (list_empty(&queue->outqueue))
> + poll_wait(file, &queue->queue_wq, wait);
> +
> + if (!list_empty(&queue->outqueue))
> + return POLLIN|POLLRDNORM;
> + return 0;
> +}
> +
> +ssize_t pd_bufqueue_read(struct pd_bufqueue *queue, unsigned int f_flags,
> + char __user *buf, size_t count)
> +{
> + int rc, retval = 0, copy = 0;
> + struct pd_frame *pd_frame = NULL;
> + unsigned long retn ;
> +
> + if (!queue->is_reading)
> + queue->is_reading = 1;
> +
> + while (count > 0) {
> + if (!queue->read_frame) {
> + retval = pd_bufqueue_dqbuf(queue,
> + f_flags, &queue->read_frame);
> + if (retval < 0)
> + goto readout;
> + }
> + pd_frame = queue->read_frame;
> +
> + rc = min(pd_frame->bytesused - queue->read_offset, count);
> + retn = copy_to_user(buf + copy,
> + pd_frame->data + queue->read_offset, rc);
> + if (retn > 0)
> + ;
> + count -= rc;
> + queue->read_offset += rc;
> + copy += rc;
> +
> + if (queue->read_offset == pd_frame->bytesused) {
> + pd_bufqueue_qbuf(queue, pd_frame->index);
> + queue->read_frame = NULL;
> + queue->read_offset = 0;
> + }
> + }
> +readout:
> + return copy ? copy : retval;
> +}
> +
> +void pd_bufqueue_cleanup(struct pd_bufqueue *queue)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < queue->buf_count; i++) {
> + if (queue->frame_buffer[i].data) {
> + if (KERNEL_MEM == queue->mtype)
> + kfree(queue->frame_buffer[i].data);
> + else
> + vfree(queue->frame_buffer[i].data);
> +
> + queue->frame_buffer[i].data = NULL;
> + }
> + }
> +}
> +
> +void pd_bufqueue_init(struct pd_bufqueue *queue, unsigned int *buf_count,
> + unsigned int buf_length, enum mem_type type)
> +{
> + unsigned int i, count, alignedsize;
> +
> + if (queue->frame_buffer[0].data) {
> + if (buf_length > queue->buf_length) {
> + pd_bufqueue_cleanup(queue);
> + } else {
> + *buf_count = queue->buf_count;
> + goto init_out;
> + }
> + }
> + memset(queue, 0, sizeof(*queue));
> + queue->buf_length = buf_length;
> +
> + count = (*buf_count > MAX_BUFFER_NUM) ? MAX_BUFFER_NUM : *buf_count;
> + alignedsize = PAGE_ALIGN(buf_length);
> +
> + for (i = 0; i < count; i++) {
> + void *mem;
> +
> + if (KERNEL_MEM == type)
> + mem = kcalloc(1, alignedsize, GFP_KERNEL);
> + else
> + mem = vmalloc_user(alignedsize);
> +
> + if (!mem)
> + break;
> + queue->frame_buffer[i].data = mem;
> + queue->frame_buffer[i].index = i;
> + INIT_LIST_HEAD(&queue->frame_buffer[i].frame);
> + }
> + queue->buf_count = *buf_count = i;
> + queue->mtype = type;
> +
> +init_out:
> + spin_lock_init(&queue->queue_lock);
> + INIT_LIST_HEAD(&queue->inqueue);
> + INIT_LIST_HEAD(&queue->outqueue);
> + init_waitqueue_head(&queue->queue_wq);
> +}
> +
> +void reset_queue_stat(struct pd_bufqueue *q)
> +{
> + unsigned long flags;
> + struct pd_frame *f = &q->frame_buffer[0];
> +
> + spin_lock_irqsave(&(q->queue_lock), flags);
> + for (; f < &q->frame_buffer[MAX_BUFFER_NUM]; f++) {
> + INIT_LIST_HEAD(&f->frame);
> + f->index = f - &q->frame_buffer[0];
> + }
> + INIT_LIST_HEAD(&q->inqueue);
> + INIT_LIST_HEAD(&q->outqueue);
> + q->read_frame = NULL;
> + q->curr_frame = NULL;
> + q->read_offset = 0;
> +
> + spin_unlock_irqrestore(&(q->queue_lock), flags);
> +}
It would be much better if you use videobuf-vmalloc, expanding it if needed.
> diff --git a/drivers/media/video/tlg2300/pd-video.c b/drivers/media/video/tlg2300/pd-video.c
> new file mode 100644
> index 0000000..f9f6e19
> --- /dev/null
> +++ b/drivers/media/video/tlg2300/pd-video.c
> @@ -0,0 +1,1636 @@
> +#include "pd-common.h"
> +#include "vendorcmds.h"
> +#include <media/v4l2-dev.h>
> +#include <linux/fs.h>
> +#include <linux/vmalloc.h>
> +#include <linux/videodev2.h>
> +#include <linux/usb.h>
> +#include <linux/mm.h>
> +#include <media/v4l2-ioctl.h>
> +#include <linux/sched.h>
> +
> +static int pd_video_release(struct file *file);
> +static int pd_video_open(struct file *file);
> +static int pm_video_open(struct file *file);
> +static int __pd_video_release(struct file *file);
> +static int pm_video_suspend(struct poseidon *pd);
> +static int pm_video_resume(struct poseidon *pd);
> +static void iso_bubble_handler(struct work_struct *w);
> +
> +int country_code = 86;
> +module_param(country_code, int, 0644);
> +MODULE_PARM_DESC(country_code, "country code (e.g China is 86)");
Huh? You shouldn't be using a country code. The driver should decide the
proper encoding based on the video stanadard.
> +
> +int usb_transfer_mode;
> +module_param(usb_transfer_mode, int, 0644);
> +MODULE_PARM_DESC(usb_transfer_mode, "0 = bulk, 1 = iscchronous");
> +
> +/*
> + * Drop frames if the hardware is not power enough
> + * and the default is the normal mode.
> + */
> +int drop_frame;
> +module_param(drop_frame, int, 0644);
> +MODULE_PARM_DESC(drop_frame, "0 = normal, 1 = drop frame");
> +
> +#define DROP_MODE (1)
> +#define FRAME_MAX_SIZE (720*576*2)
> +#define ISO_PKT_SIZE (3072)
> +
> +static const struct poseidon_format poseidon_formats[] = {
> + { "YUV 422", V4L2_PIX_FMT_YUYV, 16, 0},
> + { "RGB565", V4L2_PIX_FMT_RGB565, 16, 0},
> +};
> +static const unsigned int POSEIDON_FORMATS = ARRAY_SIZE(poseidon_formats);
> +
> +#define tv(id, _name, w, h, cmd) \
> + { \
> + .v4l2_id = id, \
> + .name = _name, \
> + .swidth = w, \
> + .sheight = h, \
> + .tlg_tvnorm = cmd, \
> + }
> +
> +static const struct poseidon_tvnorm poseidon_tvnorms[] = {
> + tv(V4L2_STD_PAL_D, "PAL-D", 720, 576, TLG_TUNE_VSTD_PAL_D),
> + tv(V4L2_STD_PAL_B, "PAL-B", 720, 576, TLG_TUNE_VSTD_PAL_B),
> + tv(V4L2_STD_PAL_G, "PAL-G", 720, 576, TLG_TUNE_VSTD_PAL_G),
> + tv(V4L2_STD_PAL_H, "PAL-H", 720, 576, TLG_TUNE_VSTD_PAL_H),
> + tv(V4L2_STD_PAL_I, "PAL-I", 720, 576, TLG_TUNE_VSTD_PAL_I),
> + tv(V4L2_STD_PAL_M, "PAL-M", 720, 480, TLG_TUNE_VSTD_PAL_M),
> + tv(V4L2_STD_PAL_N, "PAL-N", 720, 576, TLG_TUNE_VSTD_PAL_N_COMBO),
> + tv(V4L2_STD_PAL_Nc, "PAL-Nc", 720, 576, TLG_TUNE_VSTD_PAL_N_COMBO),
> + tv(V4L2_STD_NTSC_M, "NTSC-M", 720, 480, TLG_TUNE_VSTD_NTSC_M),
> + tv(V4L2_STD_NTSC_M_JP, "NTSC-JP", 720, 480, TLG_TUNE_VSTD_NTSC_M_J),
> + tv(V4L2_STD_SECAM_B, "SECAM-B", 720, 576, TLG_TUNE_VSTD_SECAM_B),
> + tv(V4L2_STD_SECAM_D, "SECAM-D", 720, 576, TLG_TUNE_VSTD_SECAM_D),
> + tv(V4L2_STD_SECAM_G, "SECAM-G", 720, 576, TLG_TUNE_VSTD_SECAM_G),
> + tv(V4L2_STD_SECAM_H, "SECAM-H", 720, 576, TLG_TUNE_VSTD_SECAM_H),
> + tv(V4L2_STD_SECAM_K, "SECAM-K", 720, 576, TLG_TUNE_VSTD_SECAM_K),
> + tv(V4L2_STD_SECAM_K1, "SECAM-K1", 720, 576, TLG_TUNE_VSTD_SECAM_K1),
> + tv(V4L2_STD_SECAM_L, "SECAM-L", 720, 576, TLG_TUNE_VSTD_SECAM_L),
> + tv(V4L2_STD_SECAM_LC, "SECAM-LC", 720, 576, TLG_TUNE_VSTD_SECAM_L1),
> +};
This is also ugly and completely uneeded. Just use V4L2_STD on all places. If
you want to know how many lines, you just need to do something like:
lines = (std && V4L2_STD_625_50) ? 576: 480;
> +static const unsigned int POSEIDON_TVNORMS = ARRAY_SIZE(poseidon_tvnorms);
> +
> +#define PD_TVNORMS_SUPPORT (V4L2_STD_PAL_D | V4L2_STD_PAL_B | V4L2_STD_PAL_G \
> + | V4L2_STD_PAL_H | V4L2_STD_PAL_I | V4L2_STD_PAL_M \
> + | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_NTSC_M \
> + | V4L2_STD_NTSC_M_JP | V4L2_STD_SECAM_B | V4L2_STD_SECAM_D \
> + | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H | V4L2_STD_SECAM_K \
> + | V4L2_STD_SECAM_K1 | V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC)
> +
> +struct pd_audio_mode {
> + u32 tlg_audio_mode;
> + u32 v4l2_audio_sub;
> + u32 v4l2_audio_mode;
> +};
> +
> +static const struct pd_audio_mode pd_audio_modes[] = {
> + { TLG_TUNE_TVAUDIO_MODE_MONO, V4L2_TUNER_SUB_MONO,
> + V4L2_TUNER_MODE_MONO },
> + { TLG_TUNE_TVAUDIO_MODE_STEREO, V4L2_TUNER_SUB_STEREO,
> + V4L2_TUNER_MODE_STEREO },
> + { TLG_TUNE_TVAUDIO_MODE_LANG_A, V4L2_TUNER_SUB_LANG1,
> + V4L2_TUNER_MODE_LANG1 },
> + { TLG_TUNE_TVAUDIO_MODE_LANG_B, V4L2_TUNER_SUB_LANG2,
> + V4L2_TUNER_MODE_LANG2 },
> + { TLG_TUNE_TVAUDIO_MODE_LANG_C, V4L2_TUNER_SUB_LANG1,
> + V4L2_TUNER_MODE_LANG1_LANG2 }
> +};
Why don't you directly use V4L2_TUNER?
> +static const unsigned int POSEIDON_AUDIOMODS = ARRAY_SIZE(pd_audio_modes);
> +
> +struct pd_input {
> + char *name;
> + uint32_t tlg_src;
> +};
> +
> +static const struct pd_input pd_inputs[] = {
> + { "TV Antenna", TLG_SIG_SRC_ANTENNA },
> + { "TV Cable", TLG_SIG_SRC_CABLE },
> + { "TV SVideo", TLG_SIG_SRC_SVIDEO },
> + { "TV Composite", TLG_SIG_SRC_COMPOSITE }
> +};
> +static const unsigned int POSEIDON_INPUTS = ARRAY_SIZE(pd_inputs);
> +
> +struct poseidon_control {
> + struct v4l2_queryctrl v4l2_ctrl;
> + enum cmd_custom_param_id vc_id;
> +};
> +
> +static struct poseidon_control controls[] = {
> + {
> + { V4L2_CID_BRIGHTNESS, V4L2_CTRL_TYPE_INTEGER,
> + "brightness", 0, 10000, 1, 100, 0, },
> + CUST_PARM_ID_BRIGHTNESS_CTRL
> + },
> +
> + {
> + { V4L2_CID_CONTRAST, V4L2_CTRL_TYPE_INTEGER,
> + "contrast", 0, 10000, 1, 100, 0, },
> + CUST_PARM_ID_CONTRAST_CTRL,
> + },
> +
> + {
> + { V4L2_CID_HUE, V4L2_CTRL_TYPE_INTEGER,
> + "hue", 0, 10000, 1, 100, 0, },
> + CUST_PARM_ID_HUE_CTRL,
> + },
> +
> + {
> + { V4L2_CID_SATURATION, V4L2_CTRL_TYPE_INTEGER,
> + "saturation", 0, 10000, 1, 100, 0, },
> + CUST_PARM_ID_SATURATION_CTRL,
> + },
> +};
> +
> +static struct v4l2_format default_v4l2_format = {
> + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
> + .fmt.pix = {
> + .width = 720,
> + .height = 576,
> + .pixelformat = V4L2_PIX_FMT_YUYV,
> + .field = V4L2_FIELD_INTERLACED,
> + .bytesperline = 720 * 2,
> + .sizeimage = 720 * 576 * 2,
> + .colorspace = V4L2_COLORSPACE_SMPTE170M,
> + .priv = 0
> + }
> +};
> +
> +static int vidioc_querycap(struct file *file, void *fh,
> + struct v4l2_capability *cap)
> +{
> + strcpy(cap->driver, "Telegent Driver");
> + strcpy(cap->card, "Telegent Poseidon");
> + strcpy(cap->bus_info, "USB Bus");
Bus info is wrong. It should be generated by something like:
usb_make_path(dev, cap->bus_info, sizeof(cap->bus_info));
> + cap->version = 0;
You should use:
cap->version = KERNEL_VERSION(0, 0, 1)
> + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER |
> + V4L2_CAP_AUDIO | V4L2_CAP_STREAMING |
> + V4L2_CAP_READWRITE;
As you have VBI support, you need also V4L2_CAP_VBI_CAPTURE
> + return 0;
> +}
> +
> +/*====================================================================*/
> +static void init_copy(struct video_data *video, bool index)
> +{
> + video->field_count = index;
> + video->lines_copied = 0;
> + video->prev_left = 0 ;
> + video->dst = (char *)video->video_queue.curr_frame->data
> + + index * video->lines_size;
> + video->vbi->copied = 0; /* set it here */
> +}
> +
> +static bool get_frame(struct pd_bufqueue *q, int *need_init)
> +{
> + if (q->curr_frame)
> + return true;
> +
> + spin_lock(&q->queue_lock);
> + if (!list_empty(&q->inqueue)) {
> + q->curr_frame = list_entry(q->inqueue.next,
> + struct pd_frame, frame);
> + list_del_init(&q->curr_frame->frame);
> + if (need_init)
> + *need_init = 1;
> + }
> + spin_unlock(&q->queue_lock);
> +
> + return !!q->curr_frame;
> +
> +}
> +
> +/* check if the video's buffer is ready */
> +static bool get_video_frame(struct video_data *video)
> +{
> + struct pd_bufqueue *q = &video->video_queue;
> + int need_init = 0;
> + bool ret = true;
> +
> + ret = get_frame(q, &need_init);
> + if (need_init)
> + init_copy(video, 0);
> + return ret;
> +}
> +
> +static void put_back_frame(struct pd_bufqueue *q)
> +{
> +
> + if (q->curr_frame == NULL)
> + return;
> +
> + spin_lock(&q->queue_lock);
> + list_add_tail(&q->curr_frame->frame, &q->outqueue);
> + q->curr_frame = NULL;
> + spin_unlock(&q->queue_lock);
> +
> + if (waitqueue_active(&q->queue_wq))
> + wake_up_interruptible(&q->queue_wq);
> +}
> +
> +static void end_field(struct video_data *video)
> +{
> + if (DROP_MODE == drop_frame) {
> + int i = 0;
> + char *buf = (char *)video->video_queue.curr_frame->data;
> + int size = video->lines_size;
> +
> + for (; i < video->lines_per_field; i++, buf += size * 2)
> + memcpy(buf + size, buf, size);
> +
> + put_back_frame(&video->video_queue);
> + return;
> + }
> +
> + if (1 == video->field_count)
> + put_back_frame(&video->video_queue);
> + else
> + init_copy(video, 1);
> +}
> +
> +static void copy_video_data(struct video_data *video, char *src,
> + unsigned int count)
> +{
> +#define copy_data(len) \
> + do { \
> + if (++video->lines_copied > video->lines_per_field) \
> + goto overflow; \
> + memcpy(video->dst, src, len);\
> + video->dst += len + video->lines_size; \
> + src += len; \
> + count -= len; \
> + } while (0)
> +
> + while (count && count >= video->lines_size) {
> + if (video->prev_left) {
> + copy_data(video->prev_left);
> + video->prev_left = 0;
> + continue;
> + }
> + copy_data(video->lines_size);
> + }
> + if (count && count < video->lines_size) {
> + memcpy(video->dst, src, count);
> +
> + video->prev_left = video->lines_size - count;
> + video->dst += count;
> + }
> + return;
> +
> +overflow:
> + end_field(video);
> +}
> +
> +static void check_trailer(struct video_data *video, char *src, int count)
> +{
> + struct vbi_data *vbi = video->vbi;
> + int offset; /* trailer's offset */
> + char *buf;
> +
> + offset = (video->cur_format.fmt.pix.sizeimage / 2 + video->vbi_size)
> + - (vbi->copied + video->lines_size * video->lines_copied);
> + if (video->prev_left)
> + offset -= (video->lines_size - video->prev_left);
> +
> + if (offset > count || offset <= 0)
> + goto short_package;
> +
> + buf = src + offset;
> +
> + /* trailer : (VFHS) + U32 + U32 + field_num */
> + if (!strncmp(buf, "VFHS", 4)) {
> + int field_num = *((u32 *)(buf + 12));
> +
> + if ((field_num & 1) ^ video->field_count) {
> + init_copy(video, video->field_count);
> + return;
> + }
> + copy_video_data(video, src, offset);
> + }
> +short_package:
> + end_field(video);
> +}
> +
> +static inline void copy_vbi_data(struct vbi_data *vbi,
> + char *src, unsigned int count)
> +{
> + if (get_frame(&vbi->vbi_queue, NULL)) {
> + char *buf = vbi->vbi_queue.curr_frame->data;
> +
> + if (vbi->video->field_count)
> + buf += vbi->video->vbi_size;
> + memcpy(buf + vbi->copied, src, count);
> + }
> + vbi->copied += count;
> +}
> +
> +/*
> + * Copy the normal data (VBI or VIDEO) without the trailer.
> + * VBI is not interlaced, while VIDEO is interlaced.
> + */
> +static inline void copy_vbi_video_data(struct video_data *video,
> + char *src, unsigned int count)
> +{
> + struct vbi_data *vbi = video->vbi;
> + unsigned int vbi_delta = video->vbi_size - vbi->copied;
> +
> + if (vbi_delta >= count) {
> + copy_vbi_data(vbi, src, count);
> + } else {
> + if (vbi_delta) {
> + copy_vbi_data(vbi, src, vbi_delta);
> + put_back_frame(&vbi->vbi_queue);
> + }
> + copy_video_data(video, src + vbi_delta, count - vbi_delta);
> + }
> +}
> +
> +static void urb_complete_bulk(struct urb *urb)
> +{
> + struct video_data *video = urb->context;
> + char *src = (char *)urb->transfer_buffer;
> + int count = urb->actual_length;
> + int ret = 0;
> +
> + if (!video->is_streaming || urb->status) {
> + if (urb->status == -EPROTO)
> + goto resend_it;
> + return;
> + }
> +
> + if (!get_video_frame(video))
> + goto resend_it;
> +
> + if (count == urb->transfer_buffer_length)
> + copy_vbi_video_data(video, src, count);
> + else
> + check_trailer(video, src, count);
> +
> +resend_it:
> + ret = usb_submit_urb(urb, GFP_ATOMIC);
> + if (ret)
> + log(" submit failed: error %d", ret);
> +}
> +
> +/************************* for ISO *********************/
> +#define GET_SUCCESS (0)
> +#define GET_TRAILER (1)
> +#define GET_TOO_MUCH_BUBBLE (2)
> +#define GET_NONE (3)
> +static int get_chunk(int start, struct urb *urb,
> + int *head, int *tail, int *bubble_err)
> +{
> + struct usb_iso_packet_descriptor *pkt = NULL;
> + int ret = GET_SUCCESS;
> +
> + for (*head = *tail = -1; start < urb->number_of_packets; start++) {
> + pkt = &urb->iso_frame_desc[start];
> +
> + /* handle the bubble of the Hub */
> + if (-EOVERFLOW == pkt->status) {
> + if (++*bubble_err > urb->number_of_packets / 3)
> + return GET_TOO_MUCH_BUBBLE;
> + continue;
> + }
> +
> + /* This is the gap */
> + if (pkt->status || pkt->actual_length <= 0
> + || pkt->actual_length > ISO_PKT_SIZE) {
> + if (*head != -1)
> + break;
> + continue;
> + }
> +
> + /* a good isochronous packet */
> + if (pkt->actual_length == ISO_PKT_SIZE) {
> + if (*head == -1)
> + *head = start;
> + *tail = start;
> + continue;
> + }
> +
> + /* trailer is here */
> + if (pkt->actual_length < ISO_PKT_SIZE) {
> + if (*head == -1) {
> + *head = start;
> + *tail = start;
> + return GET_TRAILER;
> + }
> + break;
> + }
> + }
> +
> + if (*head == -1 && *tail == -1)
> + ret = GET_NONE;
> + return ret;
> +}
> +
> +/*
> + * |__|------|___|-----|_______|
> + * ^ ^
> + * | |
> + * gap gap
> + */
> +static void urb_complete_iso(struct urb *urb)
> +{
> + struct video_data *video = urb->context;
> + int bubble_err = 0, head = 0, tail = 0;
> + char *src = (char *)urb->transfer_buffer;
> + int ret = 0;
> +
> + if (!video->is_streaming)
> + return;
> +
> + do {
> + if (!get_video_frame(video))
> + goto out;
> +
> + switch (get_chunk(head, urb, &head, &tail, &bubble_err)) {
> + case GET_SUCCESS:
> + copy_vbi_video_data(video, src + (head * ISO_PKT_SIZE),
> + (tail - head + 1) * ISO_PKT_SIZE);
> + break;
> + case GET_TRAILER:
> + check_trailer(video, src + (head * ISO_PKT_SIZE),
> + ISO_PKT_SIZE);
> + break;
> + case GET_NONE:
> + goto out;
> + case GET_TOO_MUCH_BUBBLE:
> + log("\t We got too much bubble");
> + goto out;
> + }
> + } while (head = tail + 1, head < urb->number_of_packets);
> +
> +out:
> + ret = usb_submit_urb(urb, GFP_ATOMIC);
> + if (ret)
> + log("usb_submit_urb err : %d", ret);
> +}
> +/*============================= [ end ] =====================*/
Same comment here: use videobuf instead.
> +
> +static int prepare_iso_urb(struct video_data *video)
> +{
> + int i;
> +
> + if (video->pd_sbuf[0].urb)
> + return 0;
> +
> + for (i = 0; i < SBUF_NUM; i++) {
> + int j;
> + struct urb *urb;
> +
> + urb = usb_alloc_urb(PK_PER_URB, GFP_KERNEL);
> + if (urb == NULL)
> + goto out;
> +
> + video->pd_sbuf[i].urb = urb;
> + video->pd_sbuf[i].data = usb_buffer_alloc(video->udev,
> + ISO_PKT_SIZE * PK_PER_URB,
> + GFP_KERNEL,
> + &urb->transfer_dma);
> +
> + urb->complete = urb_complete_iso; /* handler */
> + urb->dev = video->udev;
> + urb->context = video;
> + urb->pipe = usb_rcvisocpipe(video->udev,
> + video->endpoint_addr);
> + urb->interval = 1;
> + urb->number_of_packets = PK_PER_URB;
> + urb->transfer_flags = URB_ISO_ASAP;
> + urb->transfer_buffer = video->pd_sbuf[i].data;
> + urb->transfer_buffer_length = PK_PER_URB * ISO_PKT_SIZE;
> +
> + for (j = 0; j < PK_PER_URB; j++) {
> + urb->iso_frame_desc[j].offset = ISO_PKT_SIZE * j;
> + urb->iso_frame_desc[j].length = ISO_PKT_SIZE;
> + }
> + }
> + return 0;
> +out:
> + for (; i > 0; i--)
> + ;
> + return -ENOMEM;
> +}
> +
> +static int prepare_bulk_urb(struct video_data *video)
> +{
> + int i;
> + int sb_size = 0x2000 ;
> +
> + if (video->pd_sbuf[0].urb)
> + return 0;
> +
> + for (i = 0; i < SBUF_NUM; i++) {
> + struct urb *urb = usb_alloc_urb(0, GFP_KERNEL);
> +
> + if (urb == NULL)
> + return -ENOMEM;
> + video->pd_sbuf[i].urb = urb;
> + video->pd_sbuf[i].data = usb_buffer_alloc(video->udev,
> + sb_size,
> + GFP_KERNEL,
> + &urb->transfer_dma);
> + usb_fill_bulk_urb(urb, video->udev,
> + usb_rcvbulkpipe(video->udev, video->endpoint_addr),
> + video->pd_sbuf[i].data,
> + sb_size,
> + urb_complete_bulk,
> + video);
> + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> + }
> + return 0;
> +}
> +
> +int usb_transfer_start(struct video_data *video)
> +{
> + int i, ret;
> +
> + video->is_streaming = 1;
> + for (i = 0; i < SBUF_NUM; i++) {
> + ret = usb_submit_urb(video->pd_sbuf[i].urb, GFP_KERNEL);
> + if (ret)
> + log("(%d) failed: error %d", i, ret);
> + }
> + return ret;
> +}
> +
> +void usb_transfer_cleanup(struct video_data *video)
> +{
> + struct pd_sbuf *sbuf = &video->pd_sbuf[0];
> +
> + for (; sbuf < &video->pd_sbuf[SBUF_NUM]; sbuf++) {
> + if (sbuf->urb) {
> + usb_buffer_free(video->udev,
> + sbuf->urb->transfer_buffer_length,
> + sbuf->data,
> + sbuf->urb->transfer_dma);
> + usb_free_urb(sbuf->urb);
> + sbuf->urb = NULL;
> + sbuf->data = NULL;
> + }
> + }
> +}
> +
> +int usb_transfer_stop(struct video_data *video)
> +{
> + if (video->is_streaming) {
> + int i;
> +
> + video->is_streaming = 0;
> + for (i = 0; i < SBUF_NUM; ++i) {
> + if (video->pd_sbuf[i].urb)
> + usb_kill_urb(video->pd_sbuf[i].urb);
> + }
> + usb_transfer_cleanup(video);
> + }
> + return 0;
> +}
> +
> +static void usb_transfer_init(struct poseidon *pd, u8 addr,
> + struct usb_device *udev)
> +{
> + struct video_data *video = &pd->video_data;
> +
> + video->endpoint_addr = addr;
> + video->udev = udev;
> + video->vbi = &pd->vbi_data;
> + video->vbi->video = video;
> +}
> +
> +static int vidioc_enum_fmt_cap(struct file *file, void *fh,
> + struct v4l2_fmtdesc *f)
> +{
> + if (POSEIDON_FORMATS <= f->index)
> + return -EINVAL;
> + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> + f->flags = 0; /* not compressed image */
> + strcpy(f->description, poseidon_formats[f->index].name);
> + f->pixelformat = poseidon_formats[f->index].fourcc;
> + return 0;
> +}
> +
> +static int vidioc_g_fmt_cap(struct file *file, void *fh, struct v4l2_format *f)
> +{
> + struct poseidon *pd = fh;
> +
> + *f = pd->video_data.cur_format;
> + return 0;
> +}
> +
> +static int vidioc_try_fmt_cap(struct file *file, void *fh,
> + struct v4l2_format *f)
> +{
> + return 0;
> +}
> +
> +static int vidioc_s_fmt_cap(struct file *file, void *fh, struct v4l2_format *f)
> +{
> + struct poseidon *pd = fh;
> + struct v4l2_pix_format *pix = &f->fmt.pix;
> + s32 ret = 0, cmd_status = 0, vid_resol;
> +
> + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != f->type)
> + return -EINVAL;
> + if (file != pd->file_for_stream
> + && (pd->state & POSEIDON_STATE_STREAM_CAP))
> + return -EBUSY;
> +
> + mutex_lock(&pd->lock);
> + if (!(pd->state & POSEIDON_STATE_STREAM_CAP)) {
> + pd->state |= POSEIDON_STATE_STREAM_CAP;
> + pd->file_for_stream = file;
> + }
> +
> + if (file == pd->file_for_stream) {
> + if (pix->pixelformat == V4L2_PIX_FMT_RGB565) {
> + vid_resol = TLG_TUNER_VID_FORMAT_RGB_565;
> + } else {
> + pix->pixelformat = V4L2_PIX_FMT_YUYV;
> + vid_resol = TLG_TUNER_VID_FORMAT_YUV;
> + }
> + ret = send_set_req(pd, VIDEO_STREAM_FMT_SEL,
> + vid_resol, &cmd_status);
> +
> + vid_resol = TLG_TUNE_VID_RES_720;
> + switch (pix->width) {
> + case 704:
> + vid_resol = TLG_TUNE_VID_RES_704;
> + break;
> + default:
> + pix->width = 720;
> + case 720:
> + break;
> + }
> + ret |= send_set_req(pd, VIDEO_ROSOLU_SEL,
> + vid_resol, &cmd_status);
> + if (ret || cmd_status) {
> + mutex_unlock(&pd->lock);
> + return -ENXIO;
> + }
> + }
> + mutex_unlock(&pd->lock);
> +
> + pix->height = (pd->video_data.tvnormid & V4L2_STD_525_60) ? 480 : 576;
> + pix->bytesperline = pix->width * 2;
> + pix->sizeimage = pix->width * pix->height * 2;
Hmm.. if you're calculating bytesperline/sizeimage here, why had them
initialized at the struct?
> +
> + pd->video_data.cur_format = *f;
> +
> + pd->video_data.lines_per_field = pix->height / 2;
> + pd->video_data.lines_size = pix->width * 2;
> + return 0;
> +}
> +
> +static int set_std(struct poseidon *pd, v4l2_std_id *norm)
> +{
> + s32 i, ret = 0, cmd_status, param;
> +
> + for (i = 0; i < POSEIDON_TVNORMS; i++) {
> + if (*norm & poseidon_tvnorms[i].v4l2_id) {
> + param = poseidon_tvnorms[i].tlg_tvnorm;
> + goto found;
> + }
> + }
> + return -EINVAL;
> +found:
> + mutex_lock(&pd->lock);
> + ret = send_set_req(pd, VIDEO_STD_SEL, param, &cmd_status);
> + if (ret || cmd_status)
> + goto out;
> +
> + pd->video_data.tvnormid = poseidon_tvnorms[i].v4l2_id;
> + if (pd->video_data.tvnormid & V4L2_STD_525_60)
> + pd->video_data.vbi_size = V4L_NTSC_VBI_FRAMESIZE / 2;
> + else
> + pd->video_data.vbi_size = V4L_PAL_VBI_FRAMESIZE / 2;
> +out:
> + mutex_unlock(&pd->lock);
> + return ret;
> +}
> +
> +int vidioc_s_std(struct file *file, void *fh, v4l2_std_id *norm)
> +{
> + return set_std((struct poseidon *)fh, norm);
> +}
> +
> +static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *in)
> +{
> + if (in->index < 0 || in->index >= POSEIDON_INPUTS)
> + return -EINVAL;
> + strcpy(in->name, pd_inputs[in->index].name);
> + in->type = V4L2_INPUT_TYPE_TUNER;
> +
> + /*
> + * the audio input index mixed with this video input,
> + * Poseidon only have one audio/video, set to "0"
> + */
> + in->audioset = 0;
> + in->tuner = 0;
> + in->std = PD_TVNORMS_SUPPORT;
> + in->status = 0;
> + return 0;
> +}
> +
> +static int vidioc_g_input(struct file *file, void *fh, unsigned int *i)
> +{
> + struct poseidon *pd = fh;
> + *i = pd->video_data.sig_index;
> + return 0;
> +}
> +
> +static int vidioc_s_input(struct file *file, void *fh, unsigned int i)
> +{
> + s32 ret, cmd_status;
> + struct poseidon *pd = fh;
> +
> + if (i < 0 || i >= POSEIDON_INPUTS)
> + return -EINVAL;
> + ret = send_set_req(pd, SGNL_SRC_SEL,
> + pd_inputs[i].tlg_src, &cmd_status);
> + if (ret)
> + return ret;
> +
> + pd->video_data.sig_index = i;
> + return 0;
> +}
> +
> +static struct poseidon_control *check_control_id(__u32 id)
> +{
> + struct poseidon_control *control = &controls[0];
> + int array_size = ARRAY_SIZE(controls);
> +
> + for (; control < &controls[array_size]; control++)
> + if (control->v4l2_ctrl.id == id)
> + return control;
> + return NULL;
> +}
> +
> +static int vidioc_queryctrl(struct file *file, void *fh,
> + struct v4l2_queryctrl *a)
> +{
> + struct poseidon_control *control = NULL;
> +
> + control = check_control_id(a->id);
> + if (!control)
> + return -EINVAL;
> +
> + *a = control->v4l2_ctrl;
> + return 0;
> +}
> +
> +static int vidioc_g_ctrl(struct file *file, void *fh, struct v4l2_control *ctrl)
> +{
> + struct poseidon *pd = fh;
> + struct poseidon_control *control = NULL;
> + s32 ret = 0, cmd_status;
> + struct tuner_custom_parameter_s tuner_param;
> +
> + control = check_control_id(ctrl->id);
> + if (!control)
> + return -EINVAL;
> +
> + mutex_lock(&pd->lock);
> + ret = send_get_req(pd, TUNER_CUSTOM_PARAMETER, control->vc_id,
> + &tuner_param, &cmd_status, sizeof(tuner_param));
> + mutex_unlock(&pd->lock);
> +
> + if (ret || cmd_status)
> + return -1;
> +
> + ctrl->value = tuner_param.param_value;
> + return 0;
> +}
> +
> +static int vidioc_s_ctrl(struct file *file, void *fh, struct v4l2_control *a)
> +{
> + struct tuner_custom_parameter_s param = {};
> + struct poseidon_control *control = NULL;
> + s32 ret = 0, cmd_status, params;
> + struct poseidon *pd = fh;
> +
> + control = check_control_id(a->id);
> + if (!control)
> + return -EINVAL;
> +
> + param.param_value = a->value;
> + param.param_id = control->vc_id;
> + params = *(s32 *)¶m; /* temp code */
> +
> + mutex_lock(&pd->lock);
> + ret = send_set_req(pd, TUNER_CUSTOM_PARAMETER, params, &cmd_status);
> + ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status);
> + mutex_unlock(&pd->lock);
> +
> + set_current_state(TASK_INTERRUPTIBLE);
> + schedule_timeout(HZ/4);
> + return ret;
> +}
> +
> +/* Audio ioctls */
> +static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a)
> +{
> + if (0 != a->index)
> + return -EINVAL;
> + a->capability = V4L2_AUDCAP_STEREO;
> + strcpy(a->name, "USB audio in");
> + /*Poseidon have no AVL function.*/
> + a->mode = 0;
> + return 0;
> +}
> +
> +int vidioc_g_audio(struct file *file, void *fh, struct v4l2_audio *a)
> +{
> + a->index = 0;
> + a->capability = V4L2_AUDCAP_STEREO;
> + strcpy(a->name, "USB audio in");
> + a->mode = 0;
> + return 0;
> +}
> +
> +int vidioc_s_audio(struct file *file, void *fh, struct v4l2_audio *a)
> +{
> + return (0 == a->index) ? 0 : -EINVAL;
> +}
> +
> +/* Tuner ioctls */
> +static int vidioc_g_tuner(struct file *file, void *fh, struct v4l2_tuner *tuner)
> +{
> + s32 count = 5, ret, cmd_status;
> + int index;
> + struct tuner_atv_sig_stat_s atv_stat;
> + struct poseidon *pd = fh;
> +
> + if (0 != tuner->index)
> + return -EINVAL;
> +
> + mutex_lock(&pd->lock);
> + ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_ANALOG_TV,
> + &atv_stat, &cmd_status, sizeof(atv_stat));
> +
> + while (atv_stat.sig_lock_busy && count-- && !ret) {
> + set_current_state(TASK_INTERRUPTIBLE);
> + schedule_timeout(HZ);
> +
> + ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_ANALOG_TV,
> + &atv_stat, &cmd_status, sizeof(atv_stat));
> + }
> + mutex_unlock(&pd->lock);
> +
> + if (debug_mode)
> + log("P:%d,S:%d", atv_stat.sig_present, atv_stat.sig_strength);
> +
> + if (ret || cmd_status)
> + tuner->signal = 0;
> + else if (atv_stat.sig_present && !atv_stat.sig_strength)
> + tuner->signal = 0xFFFF;
> + else
> + tuner->signal = (atv_stat.sig_strength * 255 / 10) << 8;
> +
> + strcpy(tuner->name, "Telegent Systems");
> + tuner->type = V4L2_TUNER_ANALOG_TV;
> + tuner->rangelow = TUNER_FREQ_MIN / 62500;
> + tuner->rangehigh = TUNER_FREQ_MAX / 62500;
> + tuner->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO |
> + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2;
> + index = pd->video_data.audio_index;
> + tuner->rxsubchans = pd_audio_modes[index].v4l2_audio_sub;
> + tuner->audmode = pd_audio_modes[index].v4l2_audio_mode;
> + tuner->afc = 0;
> + return 0;
> +}
> +
> +static int vidioc_s_tuner(struct file *file, void *fh, struct v4l2_tuner *a)
> +{
> + s32 index, ret = 0, cmd_status, param, audiomode;
> + struct poseidon *pd = fh;
> +
> + if (0 != a->index)
> + return -EINVAL;
> +
> + for (index = 0; index < POSEIDON_AUDIOMODS; index++)
> + if (a->audmode == pd_audio_modes[index].v4l2_audio_mode)
> + goto found;
> + return -EINVAL;
> +found:
> + mutex_lock(&pd->lock);
> + param = pd_audio_modes[index].tlg_audio_mode;
> + ret = send_set_req(pd, TUNER_AUD_MODE, param, &cmd_status);
> + audiomode = get_audio_std(TLG_MODE_ANALOG_TV_UNCOMP, pd->country_code);
> + ret |= send_set_req(pd, TUNER_AUD_ANA_STD, audiomode,
> + &cmd_status);
> + if (!ret)
> + pd->video_data.audio_index = index;
> + mutex_unlock(&pd->lock);
> + return ret;
> +}
> +
> +static int vidioc_g_frequency(struct file *file, void *fh,
> + struct v4l2_frequency *freq)
> +{
> + struct poseidon *pd = fh;
> +
> + if (0 != freq->tuner)
> + return -EINVAL;
> + freq->frequency = pd->video_data.freq;
> + freq->type = V4L2_TUNER_ANALOG_TV;
> + return 0;
> +}
> +
> +static void userptr_queue_init(struct pd_bufqueue *q, int n)
> +{
> + int i = 0;
> +
> + for (i = 0; i < n; i++) {
> + INIT_LIST_HEAD(&q->frame_buffer[i].frame);
> + q->user_pages[i] = NULL;
> + }
> +
> + spin_lock_init(&q->queue_lock);
> + q->buf_count = n;
> + q->userptr_init = 0;
> + INIT_LIST_HEAD(&q->inqueue);
> + INIT_LIST_HEAD(&q->outqueue);
> + init_waitqueue_head(&q->queue_wq);
> +}
> +
> +static int pm_video_open(struct file *file)
> +{
> + pd_video_open(file);
> + return 0;
> +}
> +
> +static int set_frequency(struct poseidon *pd, __u32 frequency)
> +{
> + s32 ret = 0, param, cmd_status;
> +
> + param = frequency * 62500 / 1000;
> + if (param < TUNER_FREQ_MIN/1000 || param > TUNER_FREQ_MAX / 1000)
> + return -EINVAL;
> +
> + mutex_lock(&pd->lock);
> + ret = send_set_req(pd, TUNE_FREQ_SELECT, param, &cmd_status);
> + ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status);
> + set_current_state(TASK_INTERRUPTIBLE);
> + schedule_timeout(HZ / 4);
> + pd->video_data.freq = frequency;
> + mutex_unlock(&pd->lock);
> + return ret;
> +}
> +
> +static int vidioc_s_frequency(struct file *file, void *fh,
> + struct v4l2_frequency *freq)
> +{
> + struct poseidon *pd = fh;
> +
> + pd->file_for_stream = file;
> +#ifdef CONFIG_PM
> + pd->inode = file->f_dentry->d_inode;
> + pd->pm_suspend = pm_video_suspend;
> + pd->pm_resume = pm_video_resume;
> + pd->pm_open = pm_video_open;
> +#endif
> + return set_frequency(pd, freq->frequency);
> +}
> +
> +static int sanity_check(struct poseidon *pd, struct file *file,
> + struct v4l2_buffer *b)
> +{
> + if (pd->file_for_stream != file)
> + return -EBUSY;
> +
> + if (b->index > pd->video_data.video_queue.buf_count
> + || b->index < 0
> + || b->index >= MAX_BUFFER_NUM
> + || V4L2_BUF_TYPE_VIDEO_CAPTURE != b->type)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int vidioc_reqbufs(struct file *file, void *fh,
> + struct v4l2_requestbuffers *b)
> +{
> + u32 count = 0;
> + struct poseidon *pd = fh;
> +
> + if (file != pd->file_for_stream)
> + return -EBUSY;
> + if (pd->video_data.stream_method)
> + return -EBUSY;
> +
> + if (V4L2_MEMORY_USERPTR == b->memory) {
> + pd->video_data.buf_count = b->count;
> + pd->video_data.stream_method = STREAM_USER;
> + userptr_queue_init(&pd->video_data.video_queue, b->count);
> + } else /* if (V4L2_MEMORY_MMAP == b->memory) */ {
> + pd->video_data.stream_method = STREAM_MMAP;
> +
> + count = b->count;
> + pd_bufqueue_init(&pd->video_data.video_queue, &count,
> + pd->video_data.frame_size, USER_MEM);
> + b->count = count;
> + pd->video_data.buf_count = count;
> + }
> + return 0;
> +}
> +
> +static int vidioc_querybuf(struct file *file, void *fh, struct v4l2_buffer *b)
> +{
> + struct poseidon *pd = fh;
> +
> + if (pd->video_data.buf_count <= b->index)
> + return -EINVAL;
> +
> + b->flags = V4L2_BUF_FLAG_MAPPED;
> + b->field = V4L2_FIELD_INTERLACED;
> + b->m.offset = b->index * pd->video_data.frame_size;
> + b->length = pd->video_data.frame_size;
> + return 0;
> +}
> +
> +static void clear_user_pointer(struct pd_bufqueue *q)
> +{
> + int i, j;
> +
> + for (i = 0; i < q->buf_count; i++) {
> + if (!test_bit(i, (void *)&q->userptr_init))
> + continue;
> +
> + vunmap(q->frame_buffer[i].data);
> + q->frame_buffer[i].data = NULL;
> +
> + j = q->buf_length >> PAGE_SHIFT;
> + while (j)
> + put_page(q->user_pages[i][--j]);
> +
> + kfree(q->user_pages[i]);
> + q->user_pages[i] = NULL;
> + }
> + q->userptr_init = 0;
> +}
> +
> +static int setup_user_pointer(struct pd_bufqueue *q, struct v4l2_buffer *b)
> +{
> + unsigned int nr_pages = b->length >> PAGE_SHIFT;
> + struct pd_frame *f = &q->frame_buffer[b->index];
> + int i;
> + int ret = -ENOMEM;
> + struct page **pagesp;
> +
> + pagesp = kcalloc(1, sizeof(struct page *) * nr_pages, GFP_KERNEL);
> + if (!pagesp)
> + return -ENOMEM;
> +
> + /* <1> The function get_user_pages_fast() will
> + * 1.) increase the page->count, and
> + * 2.) makes the Anonymous page Active.
> + * 3.) makes the pages pinned in memory.
> + * 4.) prevent the pages from migrating to other node in numa.
> + */
> + get_user_pages_fast((unsigned long)b->m.userptr, nr_pages, 1, pagesp);
> + if (ret < nr_pages)
> + goto free_part_pages;
> +
> + /* <2> map the pages to VMALLOC space. */
> + f->data = vmap(pagesp, nr_pages, VM_MAP, PAGE_KERNEL);
> + if (!f->data)
> + goto free_all_pages;
> +
> + /* <3> setup the driver's buffers */
> + f->userptr = b->m.userptr;
> + f->length = b->length;
> + f->index = b->index;
> +
> + q->buf_length = b->length;
> + q->user_pages[b->index] = pagesp;
> + set_bit(b->index, (void *)&q->userptr_init);
> + return 0;
> +
> +free_all_pages:
> + ret = nr_pages;
> +free_part_pages:
> + for (i = 0; i < ret; i++)
> + put_page(pagesp[i]);
> +
> + kfree(pagesp);
> + return -EAGAIN;
> +}
> +
> +static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *b)
> +{
> + struct poseidon *pd = fh;
> + struct pd_bufqueue *q;
> + int ret = 0;
> +
> + ret = sanity_check(pd, file, b);
> + if (ret)
> + return ret;
> +
> + q = &pd->video_data.video_queue;
> +
> + if (V4L2_MEMORY_USERPTR == b->memory) {
> + if (!test_bit(b->index, (void *)&q->userptr_init))
> + setup_user_pointer(q, b);
> + pd_bufqueue_qbuf(q, b->index);
> +
> + b->flags |= V4L2_BUF_FLAG_QUEUED;
> + b->flags &= ~(V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_DONE);
> + ret = 0;
> + } else /*if (V4L2_MEMORY_MMAP == b->memory) */ {
> + ret = pd_bufqueue_qbuf(q, b->index);
> + if (!ret) {
> + b->flags |= V4L2_BUF_FLAG_QUEUED;
> + b->flags &= ~V4L2_BUF_FLAG_DONE;
> + }
> + }
> + return ret;
> +}
> +
> +static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
> +{
> + struct pd_frame *pd_frame = NULL;
> + struct poseidon *pd = fh;
> + struct video_data *video;
> + int ret;
> +
> + ret = sanity_check(pd, file, b);
> + if (ret)
> + return ret;
> +
> + video = &pd->video_data;
> + pd_bufqueue_dqbuf(&video->video_queue, file->f_flags, &pd_frame);
> + if (!pd_frame)
> + return -EAGAIN;
> +
> + b->index = pd_frame->index;
> + b->sequence = pd_frame->frame_seq;
> + b->flags &= ~(V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE);
> + b->field = V4L2_FIELD_INTERLACED;
> + do_gettimeofday(&b->timestamp);
> +
> + if (V4L2_MEMORY_USERPTR == b->memory) {
> + b->length = pd_frame->length;
> + b->m.userptr = pd_frame->userptr;
> + }
> + b->bytesused = video->cur_format.fmt.pix.sizeimage;
> +
> + /* bgr to rgb */
> + if (video->cur_format.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) {
> + u16 *buf = (u16 *)pd_frame->data;
> + for (; buf < (u16 *)(pd_frame->data + b->bytesused); buf++)
> + *buf = ((*buf) & 0x7E0)
> + | (((*buf) << 11) & 0xF800)
> + | (((*buf) >> 11) & 0x1F);
> + }
> + return 0;
> +}
> +
> +static int start_video_stream(struct poseidon *pd)
> +{
> + s32 cmd_status;
> + struct video_data *video = &pd->video_data;
> +
> + vbi_request_buf(&pd->vbi_data, pd->video_data.vbi_size * 2);
> + send_set_req(pd, TAKE_REQUEST, 0, &cmd_status);
> + send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_START, &cmd_status);
> +
> + if (video->cur_transfer_mode) {
> + prepare_iso_urb(video);
> + INIT_WORK(&pd->work, iso_bubble_handler);
> + } else {
> + prepare_bulk_urb(video);
> + }
> + printk(KERN_DEBUG "\t\t The USB transfer mode is %s now.\n\n",
> + video->cur_transfer_mode ? "isochronous" : "bulk");
> +
> + usb_transfer_start(&pd->video_data);
> + pd->video_data.is_streaming = 1;
> + return 0;
> +}
> +
> +int stop_video_stream(struct poseidon *pd)
> +{
> + if (pd->video_data.is_streaming) {
> + s32 cmd_status;
> +
> + pd->video_data.is_streaming = 0;
> + pd_bufqueue_wakeup(&pd->video_data.video_queue);
> + usb_transfer_stop(&pd->video_data);
> +
> + send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP,
> + &cmd_status);
> +
> + reset_queue_stat(&pd->video_data.video_queue);
> + reset_queue_stat(&pd->vbi_data.vbi_queue);
> + }
> + return 0;
> +}
> +
> +int pd_video_stop_stream(struct poseidon *pd)
> +{
> + mutex_lock(&pd->lock);
> + stop_video_stream(pd);
> + mutex_unlock(&pd->lock);
> + return 0;
> +}
> +
> +static void iso_bubble_handler(struct work_struct *w)
> +{
> + struct poseidon *pd = container_of(w, struct poseidon, work);
> + unsigned int i;
> +
> + mutex_lock(&pd->lock);
> + start_video_stream(pd);
> +
> + /* refill the queue */
> + for (i = 0; i < pd->video_data.buf_count; i++)
> + pd_bufqueue_qbuf(&pd->video_data.video_queue, i);
> +
> + stop_video_stream(pd);
> + mutex_unlock(&pd->lock);
> +}
> +
> +static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type i)
> +{
> + int ret = 0;
> + struct poseidon *pd = fh;
> +
> + if (file != pd->file_for_stream)
> + return -EBUSY;
> +
> + mutex_lock(&pd->lock);
> + ret = start_video_stream(pd);
> + mutex_unlock(&pd->lock);
> + return ret;
> +}
> +
> +static int vidioc_streamoff(struct file *file, void *fh, enum v4l2_buf_type i)
> +{
> + struct poseidon *pd = fh;
> +
> + if (file != pd->file_for_stream)
> + return -EBUSY;
> +
> + mutex_lock(&pd->lock);
> + stop_video_stream(pd);
> + mutex_unlock(&pd->lock);
> + return 0;
> +}
> +
> +static int pd_video_checkmode(struct poseidon *pd)
> +{
> + s32 ret = 0, cmd_status, audiomode;
> + int alternate;
> + struct video_data *video = &pd->video_data;
> +
> + set_current_state(TASK_INTERRUPTIBLE);
> + schedule_timeout(HZ/2);
> +
> + if (video->cur_transfer_mode)
> + alternate = ISO_3K_BULK_ALTERNATE_IFACE;
> + else
> + alternate = BULK_ALTERNATE_IFACE;
> + ret = usb_set_interface(pd->udev, 0, alternate);
> + if (ret < 0)
> + goto error;
> +
> + ret = set_tuner_mode(pd, TLG_MODE_ANALOG_TV);
> + ret |= send_set_req(pd, SGNL_SRC_SEL,
> + TLG_SIG_SRC_ANTENNA, &cmd_status);
> + ret |= send_set_req(pd, VIDEO_STD_SEL,
> + TLG_TUNE_VSTD_PAL_D, &cmd_status);
> + ret |= send_set_req(pd, VIDEO_STREAM_FMT_SEL,
> + TLG_TUNER_VID_FORMAT_YUV, &cmd_status);
> + ret |= send_set_req(pd, VIDEO_ROSOLU_SEL,
> + TLG_TUNE_VID_RES_720, &cmd_status);
> +
> + /* drop-field mode */
> + if (DROP_MODE == drop_frame)
> + send_set_req(pd, 0x76, 0x100, &cmd_status);
> +
> + ret |= send_set_req(pd, TUNE_FREQ_SELECT, TUNER_FREQ_MIN, &cmd_status);
> + ret |= send_set_req(pd, VBI_DATA_SEL, 1, &cmd_status);/* enable vbi */
> +
> + audiomode = get_audio_std(TLG_MODE_ANALOG_TV_UNCOMP, pd->country_code);
> + ret |= send_set_req(pd, TUNER_AUD_ANA_STD, audiomode, &cmd_status);
> + ret |= send_set_req(pd, TUNER_AUD_MODE,
> + TLG_TUNE_TVAUDIO_MODE_STEREO, &cmd_status);
> + ret |= send_set_req(pd, AUDIO_SAMPLE_RATE_SEL,
> + ATV_AUDIO_RATE_48K, &cmd_status);
> +
> + video->tvnormid = V4L2_STD_PAL_D;
> + video->cur_format = default_v4l2_format;
> + video->vbi_size = V4L_PAL_VBI_FRAMESIZE / 2;
> + video->audio_index = 1; /* STEREO */
> + video->sig_index = 0; /* Antenna */
> +error:
> + return ret;
> +}
> +
> +static int pm_video_suspend(struct poseidon *pd)
> +{
> + pm_alsa_suspend(pd);
> +
> + stop_video_stream(pd);
> + usb_transfer_cleanup(&pd->video_data);
> + usb_set_interface(pd->udev, 0, 0);
> + mdelay(2000);
> + return 0;
> +}
> +
> +static int pm_video_resume(struct poseidon *pd)
> +{
> + int i;
> + v4l2_std_id tvnorm_id;
> +
> + if (in_hibernation(pd)) {
> + __pd_video_release(pd->file_for_stream);
> + return 0;
> + }
> +
> + tvnorm_id = pd->video_data.tvnormid;
> + pd_video_checkmode(pd);
> + if (tvnorm_id != pd->video_data.tvnormid)
> + set_std(pd, &tvnorm_id);
> + vidioc_s_fmt_cap(pd->file_for_stream, pd, &pd->video_data.cur_format);
> + set_frequency(pd, pd->video_data.freq);
> +
> + for (i = 0; i < pd->video_data.buf_count; i++)
> + pd_bufqueue_qbuf(&pd->video_data.video_queue, i);
> +
> + start_video_stream(pd);
> + pm_alsa_resume(pd);
> + return 0;
> +}
> +
> +void set_debug_mode(struct video_device *vfd, int debug_mode)
> +{
> + vfd->debug = 0;
> + if (debug_mode == 1)
> + vfd->debug = V4L2_DEBUG_IOCTL;
> + if (debug_mode == 2)
> + vfd->debug = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG;
> +}
> +
> +static int pd_video_open(struct file *file)
> +{
> + s32 ret = 0;
> + struct video_device *vfd = video_devdata(file);
> + struct poseidon *pd = video_get_drvdata(vfd);
> +
> + mutex_lock(&pd->lock);
> + if (pd->state & POSEIDON_STATE_DISCONNECT) {
> + ret = -ENODEV;
> + goto out;
> + }
> + if (pd->state && !(pd->state & POSEIDON_STATE_ANALOG)) {
> + ret = -EBUSY;
> + goto out;
> + }
> +
> + usb_autopm_get_interface(pd->interface);
> + if (!pd->state) {
> + /* handle custom parameter first */
> + pd->video_data.cur_transfer_mode = usb_transfer_mode;
> + pd->country_code = country_code;
> + set_debug_mode(vfd, debug_mode);
> +
> + ret = pd_video_checkmode(pd);
> + if (ret < 0)
> + goto out;
> + pd->state |= POSEIDON_STATE_ANALOG;
> + pd->file_for_stream = file;
> + usb_transfer_init(pd, 0x82, pd->udev);
> + }
> + kref_get(&pd->kref);
> + file->private_data = pd;
> + pd->video_data.users++;
> +out:
> + mutex_unlock(&pd->lock);
> + return ret;
> +}
> +
> +static int __pd_video_release(struct file *file)
> +{
> + struct poseidon *pd = file->private_data;
> +
> + mutex_lock(&pd->lock);
> + if (file == pd->file_for_stream
> + && (pd->state & POSEIDON_STATE_STREAM_CAP)
> + && !in_hibernation(pd)) {
> + stop_video_stream(pd);
> + if (STREAM_USER == pd->video_data.stream_method)
> + clear_user_pointer(&pd->video_data.video_queue);
> + else
> + pd_bufqueue_cleanup(&pd->video_data.video_queue);
> +
> + vbi_release_buf(&pd->vbi_data);
> + usb_transfer_cleanup(&pd->video_data);
> +
> + pd->state &= ~POSEIDON_STATE_STREAM_CAP;
> + pd->video_data.stream_method = 0;
> + }
> +
> + pd->video_data.users--;
> + if (0 == pd->video_data.users)
> + pd->state = 0;
> + mutex_unlock(&pd->lock);
> +
> + kref_put(&pd->kref, poseidon_delete);
> + file->private_data = NULL;
> + return 0;
> +}
> +
> +static int pd_video_release(struct file *file)
> +{
> + struct poseidon *pd = file->private_data;
> +
> + __pd_video_release(file);
> + usb_autopm_put_interface(pd->interface);
> + return 0;
> +}
> +
> +static long pd_video_ioctl(struct file *file,
> + unsigned int cmd, unsigned long arg)
> +{
> + int country_code;
> + struct poseidon *pd = file->private_data;
> + unsigned long ret;
> +
> + if (!pd || pd->state & POSEIDON_STATE_DISCONNECT)
> + return -ENODEV;
> +
> + switch (cmd) {
> + case PD_COUNTRY_CODE:
> + ret = copy_from_user(&country_code, (char __user *)arg,
> + sizeof(country_code));
> + if (0 == ret)
> + pd->country_code = country_code;
> + return ret ? -EAGAIN : 0;
Why are you creating an ioctl for country_code? You don't need it.
> +
> + default:
> + return video_ioctl2(file, cmd, arg);
> + }
> +}
> +
> +static int pd_video_mmap(struct file *file, struct vm_area_struct *vma)
> +{
> + struct poseidon *pd = file->private_data;
> + struct pd_frame *frame;
> + int index;
> + int ret;
> +
> + if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED))
> + return -EINVAL;
> +
> + index = (vma->vm_pgoff << PAGE_SHIFT) / pd->video_data.frame_size;
> + frame = &pd->video_data.video_queue.frame_buffer[index];
> +
> + if (!frame->data)
> + return -EINVAL;
> +
> + ret = remap_vmalloc_range(vma, frame->data, 0);
> + if (ret)
> + return ret;
> + vma->vm_flags |= VM_DONTEXPAND;
> + return 0;
> +}
> +
> +unsigned int pd_video_poll(struct file *file, poll_table *table)
> +{
> + struct poseidon *pd = file->private_data;
> + return pd_bufqueue_poll(&pd->video_data.video_queue, file, table);
> +}
> +
> +ssize_t pd_video_read(struct file *file, char __user *buffer,
> + size_t count, loff_t *ppos)
> +{
> + int ret = -EINVAL;
> + struct poseidon *pd = file->private_data;
> + struct video_data *video = &pd->video_data;
> +
> + if (video->stream_method == 0 && !video->is_streaming) {
> + int buf_count = 4, i;
> +
> + pd_bufqueue_init(&video->video_queue, &buf_count,
> + video->frame_size, USER_MEM);
> + video->buf_count = buf_count;
> + for (i = 0; i < buf_count; i++)
> + pd_bufqueue_qbuf(&video->video_queue, i);
> +
> + mutex_lock(&pd->lock);
> + start_video_stream(pd);
> + mutex_unlock(&pd->lock);
> +
> + video->stream_method = STREAM_RW;
> + }
> + if (video->stream_method == STREAM_RW)
> + ret = pd_bufqueue_read(&video->video_queue, file->f_flags,
> + buffer, count);
> + return ret;
> +}
> +
> +static const struct v4l2_file_operations pd_video_fops = {
> + .owner = THIS_MODULE,
> + .open = pd_video_open,
> + .release = pd_video_release,
> + .ioctl = pd_video_ioctl,
> + .mmap = pd_video_mmap,
> + .poll = pd_video_poll,
> + .read = pd_video_read,
> +};
> +
> +static const struct v4l2_ioctl_ops pd_video_ioctl_ops = {
> + .vidioc_querycap = vidioc_querycap,
> + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_cap,
> +
> + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_cap,
> + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_cap,
> + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_cap,
> + /* Buffer handlers */
> + .vidioc_reqbufs = vidioc_reqbufs,
> + .vidioc_querybuf = vidioc_querybuf,
> + .vidioc_qbuf = vidioc_qbuf,
> + .vidioc_dqbuf = vidioc_dqbuf,
> + /* Stream on/off */
> + .vidioc_streamon = vidioc_streamon,
> + .vidioc_streamoff = vidioc_streamoff,
> +
> + .vidioc_s_std = vidioc_s_std,
> +
> + .vidioc_enum_input = vidioc_enum_input,
> + .vidioc_g_input = vidioc_g_input,
> + .vidioc_s_input = vidioc_s_input,
> +
> + /* Control handling */
> + .vidioc_queryctrl = vidioc_queryctrl,
> + .vidioc_g_ctrl = vidioc_g_ctrl,
> + .vidioc_s_ctrl = vidioc_s_ctrl,
> +
> + /* Audio ioctls */
> + .vidioc_enumaudio = vidioc_enumaudio,
> + .vidioc_g_audio = vidioc_g_audio,
> + .vidioc_s_audio = vidioc_s_audio,
> +
> + /* Tuner ioctls */
> + .vidioc_g_tuner = vidioc_g_tuner,
> + .vidioc_s_tuner = vidioc_s_tuner,
> + .vidioc_g_frequency = vidioc_g_frequency,
> + .vidioc_s_frequency = vidioc_s_frequency,
> +};
> +
> +static struct video_device pd_video_template = {
> + .name = "poseidon_video",
> + .fops = &pd_video_fops,
> + .minor = -1,
> + .release = video_device_release,
> + .tvnorms = PD_TVNORMS_SUPPORT,
> + .ioctl_ops = &pd_video_ioctl_ops,
> +};
> +
> +struct video_device *vdev_init(struct poseidon *pd, struct video_device *tmp)
> +{
> + struct video_device *vfd;
> +
> + vfd = video_device_alloc();
> + if (vfd == NULL)
> + return NULL;
> + *vfd = *tmp;
> + vfd->minor = -1;
> + vfd->parent = &(pd->udev->dev);
> + vfd->release = video_device_release;
> + video_set_drvdata(vfd, pd);
> + return vfd;
> +}
> +
> +int pd_video_init(struct poseidon *pd)
> +{
> + struct video_data *video = &pd->video_data;
> + int ret;
> +
> + video->frame_size = PAGE_ALIGN(FRAME_MAX_SIZE);
> + video->video_dev = vdev_init(pd, &pd_video_template);
> + if (video->video_dev == NULL)
> + return -ENOMEM;
> +
> + ret = video_register_device(video->video_dev, VFL_TYPE_GRABBER, -1);
> + if (ret < 0) {
> + video_device_release(video->video_dev);
> + video->video_dev = NULL;
> + return ret;
> + }
> + return 0;
> +}
> +
> +void pd_video_exit(struct poseidon *pd)
> +{
> + struct video_data *video = &pd->video_data;
> +
> + if (video->video_dev && (-1 != video->video_dev->minor)) {
> + video_unregister_device(video->video_dev);
> + video->video_dev = NULL;
> + }
> +}
Cheers,
Mauro
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 08/11] add vbi code for tlg2300
2009-11-20 3:24 ` [PATCH 08/11] add vbi code " Huang Shijie
2009-11-20 3:24 ` [PATCH 09/11] add audio support " Huang Shijie
@ 2009-12-09 18:51 ` Mauro Carvalho Chehab
1 sibling, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2009-12-09 18:51 UTC (permalink / raw)
To: Huang Shijie; +Cc: linux-media
Huang Shijie wrote:
> This is the vbi module for tlg2300.
>
> Signed-off-by: Huang Shijie <shijie8@gmail.com>
> ---
> drivers/media/video/tlg2300/pd-vbi.c | 183 ++++++++++++++++++++++++++++++++++
> 1 files changed, 183 insertions(+), 0 deletions(-)
> create mode 100644 drivers/media/video/tlg2300/pd-vbi.c
>
> diff --git a/drivers/media/video/tlg2300/pd-vbi.c b/drivers/media/video/tlg2300/pd-vbi.c
> new file mode 100644
> index 0000000..fb9ee0d
> --- /dev/null
> +++ b/drivers/media/video/tlg2300/pd-vbi.c
> @@ -0,0 +1,183 @@
> +#include <linux/kernel.h>
> +#include <linux/usb.h>
> +#include <linux/kref.h>
> +#include <linux/uaccess.h>
> +#include <linux/mm.h>
> +#include <linux/vmalloc.h>
> +#include <linux/version.h>
> +#include <media/v4l2-common.h>
> +#include <linux/types.h>
> +#include <media/v4l2-dev.h>
> +#include <linux/fs.h>
> +#include <linux/poll.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <linux/unistd.h>
> +
> +#include "vendorcmds.h"
> +#include "pd-common.h"
> +
> +int vbi_request_buf(struct vbi_data *vbi_data, size_t size)
> +{
> + int i, count = 4;
> +
> + pd_bufqueue_init(&vbi_data->vbi_queue, &count, size, KERNEL_MEM);
> +
> + for (i = 0; i < count; i++)
> + pd_bufqueue_qbuf(&vbi_data->vbi_queue, i);
> +
> + vbi_data->buf_count = count;
> + return 0;
> +}
> +
> +int vbi_release_buf(struct vbi_data *vbi_data)
> +{
> + if (vbi_data->buf_count) {
> + pd_bufqueue_cleanup(&vbi_data->vbi_queue);
> + pd_bufqueue_wakeup(&vbi_data->vbi_queue);
> + vbi_data->buf_count = 0;
> + }
> + return 0;
> +}
> +
> +static long vbi_v4l2_ioctl(struct file *file,
> + u32 cmd, unsigned long argp)
> +{
No. You should use struct v4l2_ioctl_ops for the ioctls. It should be noticed
that VBI setup should happen on _any_ V4L interface. So, the same ioctl that
sets video, should also set VBI.
So, the code bellow should be merged with the video part of the driver.
> + struct poseidon *pd = file->private_data;
> + int ret = 0;
> +
> + mutex_lock(&pd->lock);
> + switch (cmd) {
> + case VIDIOC_QUERYCAP: {
> + struct v4l2_capability *t_cap = (struct v4l2_capability *)argp;
> +
> + strcpy(t_cap->driver, "Telegent Driver");
> + strcpy(t_cap->card, "Telegent Poseidon");
> + strcpy(t_cap->bus_info, "USB bus");
> + t_cap->version = 0;
> + t_cap->capabilities = V4L2_CAP_VBI_CAPTURE;
> + }
> + break;
> +
> + case VIDIOC_S_FMT:
> + case VIDIOC_G_FMT: {
> + struct v4l2_format *v4l2_f = (struct v4l2_format *)argp;
> +
> + v4l2_f->fmt.vbi.sampling_rate = 6750000 * 4;
> + v4l2_f->fmt.vbi.samples_per_line = 720*2/* VBI_LINE_LENGTH */;
> + v4l2_f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
> + v4l2_f->fmt.vbi.offset = 64 * 4; /*FIXME: why offset */
> + if (pd->video_data.tvnormid & V4L2_STD_525_60) {
> + v4l2_f->fmt.vbi.start[0] = 10;
> + v4l2_f->fmt.vbi.start[1] = 264;
> + v4l2_f->fmt.vbi.count[0] = V4L_NTSC_VBI_LINES;
> + v4l2_f->fmt.vbi.count[1] = V4L_NTSC_VBI_LINES;
> + } else {
> + v4l2_f->fmt.vbi.start[0] = 6;
> + v4l2_f->fmt.vbi.start[1] = 314;
> + v4l2_f->fmt.vbi.count[0] = V4L_PAL_VBI_LINES;
> + v4l2_f->fmt.vbi.count[1] = V4L_PAL_VBI_LINES;
> + }
> + /* VBI_UNSYNC VBI_INTERLACED */
> + v4l2_f->fmt.vbi.flags = V4L2_VBI_UNSYNC;
> + }
> + break;
> +
> + default:
> + ret = -EINVAL;
> + }
> + mutex_unlock(&pd->lock);
> + return ret;
> +}
> +
> +static ssize_t vbi_read(struct file *file, char __user *buffer,
> + size_t count, loff_t *ppos)
> +{
> + struct poseidon *pd = file->private_data;
> + struct vbi_data *vbi = &pd->vbi_data;
> +
> + return vbi->buf_count ?
> + pd_bufqueue_read(&vbi->vbi_queue, file->f_flags, buffer, count)
> + : -EINVAL;
> +}
> +
> +static int vbi_open(struct file *file)
> +{
> + int ret = 0;
> + struct video_device *vd = video_devdata(file);
> + struct poseidon *pd = video_get_drvdata(vd);
> +
> + if (!pd)
> + return -ENODEV;
> +
> + mutex_lock(&pd->lock);
> + if (pd->state & POSEIDON_STATE_DISCONNECT) {
> + ret = -ENODEV;
> + goto out;
> + }
> + kref_get(&pd->kref);
> + file->private_data = pd;
> + set_debug_mode(vd, debug_mode);
> +out:
> + mutex_unlock(&pd->lock);
> + return ret;
> +}
> +
> +static int vbi_release(struct file *file)
> +{
> + struct poseidon *pd = file->private_data;
> +
> + if (!pd)
> + return -ENODEV;
> +
> + kref_put(&pd->kref, poseidon_delete);
> + return 0;
> +}
> +
> +static const struct v4l2_file_operations vbi_file_ops = {
> + .owner = THIS_MODULE,
> + .open = vbi_open,
> + .release = vbi_release,
> + .read = vbi_read,
> + .ioctl = vbi_v4l2_ioctl,
> +};
> +
> +static struct video_device vbi_device = {
> + .name = "poseidon_vbi",
> + .fops = &vbi_file_ops,
> + .release = video_device_release,
> +};
> +
> +int vbi_init(struct poseidon *pd)
> +{
> + int ret = 0;
> +
> + memset(&(pd->vbi_data), 0, sizeof(struct vbi_data));
> +
> + pd->vbi_data.vbi_dev = vdev_init(pd, &vbi_device);
> + if (pd->vbi_data.vbi_dev == NULL) {
> + ret = -ENOMEM;
> + goto vbi_error;
> + }
> +
> + if (video_register_device(pd->vbi_data.vbi_dev, VFL_TYPE_VBI, -1) < 0) {
> + video_device_release(pd->vbi_data.vbi_dev);
> + pd->vbi_data.vbi_dev = NULL;
> + printk(KERN_DEBUG"vbi_init : video device register failed\n");
> + ret = -1;
> + }
> +
> +vbi_error:
> + return ret;
> +}
> +
> +int vbi_exit(struct poseidon *pd)
> +{
> + struct vbi_data *vbi_data = &pd->vbi_data;
> +
> + if (vbi_data->vbi_dev) {
> + video_unregister_device(vbi_data->vbi_dev);
> + vbi_data->vbi_dev = NULL;
> + }
> + return 0;
> +}
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 09/11] add audio support for tlg2300
2009-11-20 3:24 ` [PATCH 09/11] add audio support " Huang Shijie
2009-11-20 3:24 ` [PATCH 10/11] add DVB-T " Huang Shijie
@ 2009-12-09 18:52 ` Mauro Carvalho Chehab
1 sibling, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2009-12-09 18:52 UTC (permalink / raw)
To: Huang Shijie; +Cc: linux-media
Huang Shijie wrote:
> The module uses ALSA for the audio, it will register
> a new card for tlg2300.
This one seems ok, but it is better to c/c alsa ML also, when adding a new
alsa module, for they to help reviewing it.
>
> Signed-off-by: Huang Shijie <shijie8@gmail.com>
> ---
> drivers/media/video/tlg2300/pd-alsa.c | 379 +++++++++++++++++++++++++++++++++
> 1 files changed, 379 insertions(+), 0 deletions(-)
> create mode 100644 drivers/media/video/tlg2300/pd-alsa.c
>
> diff --git a/drivers/media/video/tlg2300/pd-alsa.c b/drivers/media/video/tlg2300/pd-alsa.c
> new file mode 100644
> index 0000000..54a5aaa
> --- /dev/null
> +++ b/drivers/media/video/tlg2300/pd-alsa.c
> @@ -0,0 +1,379 @@
> +#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 ANOLOG_AUDIO_ID (0)
> +#define FM_ID (1)
> +#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;
> + kref_put(&p->kref, poseidon_delete);
> + usb_autopm_put_interface(p->interface);
> + 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++) {
> + struct urb *urb = pa->urb[i];
> +
> + if (!urb)
> + continue;
> + usb_buffer_free(p->udev, urb->transfer_buffer_length,
> + urb->transfer_buffer, urb->transfer_dma);
> + usb_free_urb(urb);
> + pa->urb[i] = NULL;
> + }
> + return 0;
> +}
> +
> +static int audio_buf_init(struct poseidon *p)
> +{
> + int i, ret;
> + struct poseidon_audio *pa = &p->audio;
> +
> + for (i = 0; i < AUDIO_BUFS; i++) {
> + struct urb *urb;
> + char *mem = NULL;
> +
> + if (pa->urb[i])
> + continue;
> +
> + urb = usb_alloc_urb(0, GFP_ATOMIC);
> + if (!urb) {
> + ret = -ENOMEM;
> + goto init_err;
> + }
> + mem = usb_buffer_alloc(p->udev, AUDIO_BUF_SIZE,
> + GFP_ATOMIC, &urb->transfer_dma);
> + if (!mem) {
> + usb_free_urb(urb);
> + goto init_err;
> + }
> + usb_fill_bulk_urb(urb, p->udev,
> + usb_rcvbulkpipe(p->udev, AUDIO_EP),
> + mem, AUDIO_BUF_SIZE,
> + complete_handler_audio,
> + pa);
> + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> + pa->urb[i] = urb;
> + }
> + return 0;
> +
> +init_err:
> + audio_buf_free(p);
> + return ret;
> +}
> +
> +static int snd_pd_hw_capture_free(struct snd_pcm_substream *substream)
> +{
> + struct poseidon *p = snd_pcm_substream_chip(substream);
> +
> + 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_OFF)
> + return;
> +
> + if (urb->status != 0) {
> + /*if (urb->status == -ESHUTDOWN)*/
> + return;
> + }
> +
> + if (pa->capture_stream == STREAM_ON && substream && !urb->status) {
> + 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 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;
> + int i, ret;
> +
> + if (debug_mode)
> + log("cmd %d\n", cmd);
> +
> + switch (cmd) {
> + case SNDRV_PCM_TRIGGER_RESUME:
> + case SNDRV_PCM_TRIGGER_START:
> + if (audio_in_hibernate(p))
> + return 0;
> + if (pa->capture_stream == STREAM_ON)
> + return 0;
> +
> + pa->rcv_position = pa->copied_position = 0;
> + pa->capture_stream = STREAM_ON;
> +
> + audio_buf_init(p);
> + for (i = 0; i < AUDIO_BUFS; i++) {
> + ret = usb_submit_urb(pa->urb[i], GFP_KERNEL);
> + if (ret)
> + log("urb err : %d", ret);
> + }
> + return 0;
> + case SNDRV_PCM_TRIGGER_SUSPEND:
> + 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)
> +{
> + struct poseidon_audio *pa = &p->audio;
> + int i;
> +
> + snd_pcm_suspend(pa->capture_pcm_substream);
> +
> + for (i = 0; i < AUDIO_BUFS; i++)
> + usb_kill_urb(pa->urb[i]);
> +
> + audio_buf_free(p);
> + return 0;
> +}
> +
> +int pm_alsa_resume(struct poseidon *p)
> +{
> + struct poseidon_audio *pa = &p->audio;
> +
> + if (audio_in_hibernate(p)) {
> + pa->pm_state = 0;
> + usb_autopm_get_interface(p->interface);
> + }
> + snd_pd_capture_trigger(pa->capture_pcm_substream,
> + SNDRV_PCM_TRIGGER_START);
> + 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;
> +
> + if (audio_in_hibernate(p))
> + return 0;
> +
> + ret = snd_card_create(-1, "poseidon_audio", 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 (audio_in_hibernate(p))
> + return 0;
> +
> + if (pa->card)
> + snd_card_free(pa->card);
> + return 0;
> +}
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 10/11] add DVB-T support for tlg2300
2009-11-20 3:24 ` [PATCH 10/11] add DVB-T " Huang Shijie
2009-11-20 3:24 ` [PATCH 11/11] add FM " Huang Shijie
@ 2009-12-09 18:56 ` Mauro Carvalho Chehab
1 sibling, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2009-12-09 18:56 UTC (permalink / raw)
To: Huang Shijie; +Cc: linux-media
Huang Shijie wrote:
> This module contains the code for DVB-T.
>
> Signed-off-by: Huang Shijie <shijie8@gmail.com>
> ---
> drivers/media/video/tlg2300/pd-dvb.c | 649 ++++++++++++++++++++++++++++++++++
> 1 files changed, 649 insertions(+), 0 deletions(-)
> create mode 100644 drivers/media/video/tlg2300/pd-dvb.c
>
> diff --git a/drivers/media/video/tlg2300/pd-dvb.c b/drivers/media/video/tlg2300/pd-dvb.c
> new file mode 100644
> index 0000000..933fff0
> --- /dev/null
> +++ b/drivers/media/video/tlg2300/pd-dvb.c
> @@ -0,0 +1,649 @@
> +#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)
> + 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 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);
> + mdelay(2000);
Use msleep()
> + return 0;
> +}
> +
> +
> +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;
> +}
> +
> +static int pm_dvb_resume(struct poseidon *pd)
> +{
> + struct pd_dvb_adapter *pd_dvb = &pd->dvb_data;
> +
> + if (in_hibernation(pd))
> + return 0;
> +
> + /* recovery for hibernation */
> + if (pd->interface->pm_usage_cnt <= 0)
> + usb_autopm_get_interface(pd->interface);
> +
> + poseidon_check_mode_dvbt(pd);
> + mdelay(1000);
Don't use high delays. The delay runs a NOP loop, spending energy and holding the machine.
Use, instead, msleep(1000).
> + poseidon_set_fe(&pd_dvb->dvb_fe, &pd_dvb->fe_param);
> +
> + dvb_start_streaming(pd_dvb);
> + return 0;
> +}
> +
> +static int pm_dvb_open(struct file *file) { return 0; }
> +
> +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;
> + pd->pm_open = pm_dvb_open;
> +#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)
> +{
> + struct pd_sbuf *sbuf = &pd_dvb->dvb_sbuf[0];
> + struct urb *urb;
> +
> + if (sbuf->urb)
> + return 0;
> +
> + for (; sbuf < &pd_dvb->dvb_sbuf[DVB_SBUF_NUM]; sbuf++) {
> + urb = usb_alloc_urb(0, GFP_KERNEL);
> + if (urb == NULL)
> + return -ENOMEM;
> + sbuf->urb = urb;
> + sbuf->data = usb_buffer_alloc(pd_dvb->udev,
> + DVB_URB_BUF_SIZE,
> + GFP_KERNEL,
> + &urb->transfer_dma);
> +
> + usb_fill_bulk_urb(urb, pd_dvb->udev,
> + usb_rcvbulkpipe(pd_dvb->udev, pd_dvb->bulk_endAddre),
> + sbuf->data, DVB_URB_BUF_SIZE, dvb_urb_irq, pd_dvb);
> + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> + }
> + return 0;
> +}
> +
> +static void dvb_urb_cleanup(struct pd_dvb_adapter *pd_dvb)
> +{
> + struct pd_sbuf *sbuf = &pd_dvb->dvb_sbuf[0];
> +
> + for (; sbuf < &pd_dvb->dvb_sbuf[DVB_SBUF_NUM]; sbuf++) {
> + if (sbuf->urb) {
> + usb_buffer_free(pd_dvb->udev,
> + sbuf->urb->transfer_buffer_length,
> + sbuf->data,
> + sbuf->urb->transfer_dma);
> + usb_free_urb(sbuf->urb);
> + sbuf->urb = NULL;
> + sbuf->data = NULL;
> + }
> + }
> +}
> +
> +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->dvb_sbuf[i].urb,
> + 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->dvb_sbuf[i].urb)
> + usb_kill_urb(pd_dvb->dvb_sbuf[i].urb);
> +
> + ret = send_set_req(pd, PLAY_SERVICE, 2, &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;
> +}
> +
> +static void dvb_hibernate_set(struct pd_dvb_adapter *pd_dvb)
> +{
> + pd_dvb->reserved[1] = 1;
> +}
> +static void dvb_hibernate_clear(struct pd_dvb_adapter *pd_dvb)
> +{
> + pd_dvb->reserved[1] = 0;
> +}
> +static bool dvb_in_hibernate(struct pd_dvb_adapter *pd_dvb)
> +{
> + return pd_dvb->reserved[1];
> +}
> +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;
> +
> + if (dvb_in_hibernate(pd_dvb)) {
> + pd_dvb->udev = pd->udev;
> + dvb_hibernate_clear(pd_dvb);
> + return 0;
> + }
> +
> + pd_dvb->bulk_endAddre = 0x82;
> + atomic_set(&pd_dvb->users, 0);
> + atomic_set(&pd_dvb->active_feed, 0);
> + pd_dvb->pd_device = pd;
> + pd_dvb->udev = pd->udev;
> +
> + 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;
> +
> + if (atomic_read(&pd_dvb->users) > 0 && in_hibernation(pd)) {
> + dvb_hibernate_set(pd_dvb);
> + return;
> + }
> +
> + 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;
> +}
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 11/11] add FM support for tlg2300
2009-11-20 3:24 ` [PATCH 11/11] add FM " Huang Shijie
@ 2009-12-09 19:04 ` Mauro Carvalho Chehab
0 siblings, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2009-12-09 19:04 UTC (permalink / raw)
To: Huang Shijie; +Cc: linux-media
Huang Shijie wrote:
> This module contains codes for radio.
> The radio use the ALSA audio as input.
>
> The mplayer should be compiled with --enable-radio and
> --enable-radio-capture.
> The command runs as this(assume the alsa audio registers to card 1):
> #mplayer radio://103.7/capture/ -radio
> adevice=hw=1,0:arate=48000 -rawaudio rate=48000:channels=2
>
> Signed-off-by: Huang Shijie <shijie8@gmail.com>
> ---
> drivers/media/video/tlg2300/pd-radio.c | 383 ++++++++++++++++++++++++++++++++
> 1 files changed, 383 insertions(+), 0 deletions(-)
> create mode 100644 drivers/media/video/tlg2300/pd-radio.c
>
> diff --git a/drivers/media/video/tlg2300/pd-radio.c b/drivers/media/video/tlg2300/pd-radio.c
> new file mode 100644
> index 0000000..2576f3a
> --- /dev/null
> +++ b/drivers/media/video/tlg2300/pd-radio.c
> @@ -0,0 +1,383 @@
> +#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);
> +static int __poseidon_fm_close(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;
> +}
> +
> +static int pm_fm_suspend(struct poseidon *p)
> +{
> + pm_alsa_suspend(p);
> + usb_set_interface(p->udev, 0, 0);
> + mdelay(2000);
> + return 0;
> +}
> +
> +static int pm_fm_resume(struct poseidon *p)
> +{
> + if (in_hibernation(p)) {
> + __poseidon_fm_close(p->file_for_stream);
> + return 0;
> + }
> + poseidon_check_mode_radio(p);
> + set_frequency(p, p->radio_data.fm_freq);
> + pm_alsa_resume(p);
> + return 0;
> +}
> +
> +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)
> + 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);
> + }
> + mutex_unlock(&p->lock);
> +
> + kref_put(&p->kref, poseidon_delete);
> + filp->private_data = NULL;
> + return 0;
> +}
> +
> +static int poseidon_fm_close(struct file *filp)
> +{
> + struct poseidon *p = filp->private_data;
> +
> + __poseidon_fm_close(filp);
> + usb_autopm_put_interface(p->interface);
> + return 0;
> +}
> +
> +static int vidioc_querycap(struct file *file, void *priv,
> + struct v4l2_capability *v)
> +{
> + strlcpy(v->driver, "radio-tele", sizeof(v->driver));
> + strlcpy(v->card, "telegent Radio", sizeof(v->card));
> + sprintf(v->bus_info, "USB");
> + v->version = 0;
> + v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
> + return 0;
> +}
The same comments I made to the video part applies here: bus_info/version/capabilities are wrong.
Also, V4L2 spec says that you should be able to access the radio mode on any interface
(yes, you can use /dev/video0 to listen to radio).
Not all drivers implement it correctly, though.
> +
> +static long pd_radio_ioctls(struct file *file,
> + unsigned int cmd, unsigned long arg)
> +{
> + struct poseidon *p = file->private_data;
> + unsigned long ret;
> + int country_code;
> +
> + if (in_hibernation(p))
> + return -1;
> +
> + if (cmd == PD_COUNTRY_CODE) {
> + ret = copy_from_user(&country_code, (void __user *)arg,
> + sizeof(country_code));
> + if (0 == ret) {
> + p->country_code = country_code;
> + return 0;
> + } else
> + return -EAGAIN;
> + }
Don't add a country_code. The FM range can be specified via V4L2 calls,
and I think we ended by adding a ctrl to set the pre-emphasis. Userspace
should set pre-emphasis/FM range based on a country code set it might have.
> + return video_ioctl2(file, cmd, arg);
> +}
> +
> +static const struct v4l2_file_operations poseidon_fm_fops = {
> + .owner = THIS_MODULE,
> + .open = poseidon_fm_open,
> + .release = poseidon_fm_close,
> + .ioctl = pd_radio_ioctls,
> +};
> +
> +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->inode = file->f_dentry->d_inode;
> + p->pm_open = poseidon_fm_open;
> + 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-FM",
> + .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)
> +{
> + if (p->radio_data.fm_dev) {
> + video_unregister_device(p->radio_data.fm_dev);
> + p->radio_data.fm_dev = NULL;
> + }
> + return 0;
> +}
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 00/11] add linux driver for chip TLG2300
2009-11-20 3:24 [PATCH 00/11] add linux driver for chip TLG2300 Huang Shijie
2009-11-20 3:24 ` [PATCH 01/11] modify video's Kconfig and Makefile for tlg2300 Huang Shijie
@ 2009-12-09 19:08 ` Mauro Carvalho Chehab
2010-01-11 13:24 ` Mauro Carvalho Chehab
1 sibling, 1 reply; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2009-12-09 19:08 UTC (permalink / raw)
To: Huang Shijie; +Cc: linux-media
Huang Shijie wrote:
> The TLG2300 is a chip of Telegent System.
> It support analog tv,DVB-T and radio in a single chip.
> The chip has been used in several dongles, such as aeromax DH-9000:
> http://www.b2bdvb.com/dh-9000.htm
>
> You can get more info from:
> [1] http://www.telegent.com/
> [2] http://www.telegent.com/press/2009Sept14_CSI.html
>
> Huang Shijie (10):
> add maitainers for tlg2300
> add readme file for tlg2300
> add Kconfig and Makefile for tlg2300
> add header files for tlg2300
> add the generic file
> add video file for tlg2300
> add vbi code for tlg2300
> add audio support for tlg2300
> add DVB-T support for tlg2300
> add FM support for tlg2300
>
Ok, finished reviewing it.
Patches 01, 02 and 04 seems ok to me. You didn't sent a patch 03.
Patch 05 will likely need some changes (the headers) due to some reviews I did
on the other patches.
The other patches need some adjustments, as commented on separate emails.
Cheers,
Mauro.
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 00/11] add linux driver for chip TLG2300
2009-12-09 19:08 ` [PATCH 00/11] add linux driver for chip TLG2300 Mauro Carvalho Chehab
@ 2010-01-11 13:24 ` Mauro Carvalho Chehab
2010-01-12 1:52 ` Huang Shijie
0 siblings, 1 reply; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2010-01-11 13:24 UTC (permalink / raw)
To: Mauro Carvalho Chehab; +Cc: Huang Shijie, linux-media
Em Wed, 09 Dec 2009 17:08:27 -0200
Mauro Carvalho Chehab <mchehab@redhat.com> escreveu:
> Huang Shijie wrote:
> > The TLG2300 is a chip of Telegent System.
> > It support analog tv,DVB-T and radio in a single chip.
> > The chip has been used in several dongles, such as aeromax DH-9000:
> > http://www.b2bdvb.com/dh-9000.htm
> >
> > You can get more info from:
> > [1] http://www.telegent.com/
> > [2] http://www.telegent.com/press/2009Sept14_CSI.html
> >
> > Huang Shijie (10):
> > add maitainers for tlg2300
> > add readme file for tlg2300
> > add Kconfig and Makefile for tlg2300
> > add header files for tlg2300
> > add the generic file
> > add video file for tlg2300
> > add vbi code for tlg2300
> > add audio support for tlg2300
> > add DVB-T support for tlg2300
> > add FM support for tlg2300
> >
>
> Ok, finished reviewing it.
>
> Patches 01, 02 and 04 seems ok to me. You didn't sent a patch 03.
> Patch 05 will likely need some changes (the headers) due to some reviews I did
> on the other patches.
>
> The other patches need some adjustments, as commented on separate emails.
>
Hi Huang,
Happy new year!
Had you finish fixing the pointed issues?
Cheers,
Mauro
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH 00/11] add linux driver for chip TLG2300
2010-01-11 13:24 ` Mauro Carvalho Chehab
@ 2010-01-12 1:52 ` Huang Shijie
0 siblings, 0 replies; 21+ messages in thread
From: Huang Shijie @ 2010-01-12 1:52 UTC (permalink / raw)
To: Mauro Carvalho Chehab; +Cc: Mauro Carvalho Chehab, linux-media
> Em Wed, 09 Dec 2009 17:08:27 -0200
> Mauro Carvalho Chehab<mchehab@redhat.com> escreveu:
>
>
>> Huang Shijie wrote:
>>
>>> The TLG2300 is a chip of Telegent System.
>>> It support analog tv,DVB-T and radio in a single chip.
>>> The chip has been used in several dongles, such as aeromax DH-9000:
>>> http://www.b2bdvb.com/dh-9000.htm
>>>
>>> You can get more info from:
>>> [1] http://www.telegent.com/
>>> [2] http://www.telegent.com/press/2009Sept14_CSI.html
>>>
>>> Huang Shijie (10):
>>> add maitainers for tlg2300
>>> add readme file for tlg2300
>>> add Kconfig and Makefile for tlg2300
>>> add header files for tlg2300
>>> add the generic file
>>> add video file for tlg2300
>>> add vbi code for tlg2300
>>> add audio support for tlg2300
>>> add DVB-T support for tlg2300
>>> add FM support for tlg2300
>>>
>>>
>> Ok, finished reviewing it.
>>
>> Patches 01, 02 and 04 seems ok to me. You didn't sent a patch 03.
>> Patch 05 will likely need some changes (the headers) due to some reviews I did
>> on the other patches.
>>
>> The other patches need some adjustments, as commented on separate emails.
>>
>>
> Hi Huang,
>
> Happy new year!
>
>
Happy new year :)
> Had you finish fixing the pointed issues?
>
>
My email system had a little problem, and losted some important emails.
I missed your email unfortunately. :(
I will do the change according your email as soon as possible.
I will sent out the new version when i finish it.
> Cheers,
> Mauro
>
>
best regards
Huang Shijie
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2010-01-12 1:59 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2009-11-20 3:24 [PATCH 00/11] add linux driver for chip TLG2300 Huang Shijie
2009-11-20 3:24 ` [PATCH 01/11] modify video's Kconfig and Makefile for tlg2300 Huang Shijie
2009-11-20 3:24 ` [PATCH 02/11] add maitainers " Huang Shijie
[not found] ` <1258687493-4012-4-git-send-email-shijie8@gmail.com>
2009-11-20 3:24 ` [PATCH 04/11] add Kconfig and Makefile " Huang Shijie
2009-11-20 3:24 ` [PATCH 05/11] add header files " Huang Shijie
2009-11-20 3:24 ` [PATCH 06/11] add the generic file Huang Shijie
2009-11-20 3:24 ` [PATCH 07/11] add video file for tlg2300 Huang Shijie
2009-11-20 3:24 ` [PATCH 08/11] add vbi code " Huang Shijie
2009-11-20 3:24 ` [PATCH 09/11] add audio support " Huang Shijie
2009-11-20 3:24 ` [PATCH 10/11] add DVB-T " Huang Shijie
2009-11-20 3:24 ` [PATCH 11/11] add FM " Huang Shijie
2009-12-09 19:04 ` Mauro Carvalho Chehab
2009-12-09 18:56 ` [PATCH 10/11] add DVB-T " Mauro Carvalho Chehab
2009-12-09 18:52 ` [PATCH 09/11] add audio " Mauro Carvalho Chehab
2009-12-09 18:51 ` [PATCH 08/11] add vbi code " Mauro Carvalho Chehab
2009-12-09 18:48 ` [PATCH 07/11] add video file " Mauro Carvalho Chehab
2009-11-23 3:25 ` [PATCH 06/11] add the generic file Huang Shijie
2009-12-09 18:12 ` Mauro Carvalho Chehab
2009-12-09 19:08 ` [PATCH 00/11] add linux driver for chip TLG2300 Mauro Carvalho Chehab
2010-01-11 13:24 ` Mauro Carvalho Chehab
2010-01-12 1:52 ` Huang Shijie
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox