diff options
Diffstat (limited to 'drivers/media/usb/pvrusb2')
46 files changed, 14846 insertions, 0 deletions
diff --git a/drivers/media/usb/pvrusb2/Kconfig b/drivers/media/usb/pvrusb2/Kconfig new file mode 100644 index 0000000000..0df10270db --- /dev/null +++ b/drivers/media/usb/pvrusb2/Kconfig @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_PVRUSB2 + tristate "Hauppauge WinTV-PVR USB2 support" + depends on VIDEO_DEV && I2C && DVB_CORE + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_CX2341X + select VIDEO_SAA711X + select VIDEO_CX25840 + select VIDEO_MSP3400 + select VIDEO_WM8775 + select VIDEO_CS53L32A + help + This is a video4linux driver for Conexant 23416 based + usb2 personal video recorder devices. + + To compile this driver as a module, choose M here: the + module will be called pvrusb2 + +config VIDEO_PVRUSB2_SYSFS + bool "pvrusb2 sysfs support" + default y + depends on VIDEO_PVRUSB2 && SYSFS + help + This option enables the operation of a sysfs based + interface for query and control of the pvrusb2 driver. + + This is not generally needed for v4l applications, + although certain applications are optimized to take + advantage of this feature. + + If you are in doubt, say Y. + + Note: This feature is experimental and subject to change. + +config VIDEO_PVRUSB2_DVB + bool "pvrusb2 ATSC/DVB support" + default y + depends on VIDEO_PVRUSB2 && DVB_CORE + depends on VIDEO_PVRUSB2=m || DVB_CORE=y + select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT + select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT + select DVB_TDA10048 if MEDIA_SUBDRV_AUTOSELECT + select DVB_LGDT3306A if MEDIA_SUBDRV_AUTOSELECT + select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA8290 if MEDIA_SUBDRV_AUTOSELECT + help + This option enables a DVB interface for the pvrusb2 driver. + If your device does not support digital television, this + feature will have no affect on the driver's operation. + + If you are in doubt, say Y. + +config VIDEO_PVRUSB2_DEBUGIFC + bool "pvrusb2 debug interface" + depends on VIDEO_PVRUSB2_SYSFS + help + This option enables the inclusion of a debug interface + in the pvrusb2 driver, hosted through sysfs. + + You do not need to select this option unless you plan + on debugging the driver or performing a manual firmware + extraction. + + If you are in doubt, say N. diff --git a/drivers/media/usb/pvrusb2/Makefile b/drivers/media/usb/pvrusb2/Makefile new file mode 100644 index 0000000000..2e71afc4f6 --- /dev/null +++ b/drivers/media/usb/pvrusb2/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-pvrusb2-sysfs-$(CONFIG_VIDEO_PVRUSB2_SYSFS) := pvrusb2-sysfs.o +obj-pvrusb2-debugifc-$(CONFIG_VIDEO_PVRUSB2_DEBUGIFC) := pvrusb2-debugifc.o +obj-pvrusb2-dvb-$(CONFIG_VIDEO_PVRUSB2_DVB) := pvrusb2-dvb.o + +pvrusb2-objs := pvrusb2-i2c-core.o \ + pvrusb2-audio.o \ + pvrusb2-encoder.o pvrusb2-video-v4l.o \ + pvrusb2-eeprom.o \ + pvrusb2-main.o pvrusb2-hdw.o pvrusb2-v4l2.o \ + pvrusb2-ctrl.o pvrusb2-std.o pvrusb2-devattr.o \ + pvrusb2-context.o pvrusb2-io.o pvrusb2-ioread.o \ + pvrusb2-cx2584x-v4l.o pvrusb2-wm8775.o \ + pvrusb2-cs53l32a.o \ + $(obj-pvrusb2-dvb-y) \ + $(obj-pvrusb2-sysfs-y) $(obj-pvrusb2-debugifc-y) + +obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2.o + +ccflags-y += -I $(srctree)/drivers/media/tuners +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends diff --git a/drivers/media/usb/pvrusb2/pvrusb2-audio.c b/drivers/media/usb/pvrusb2/pvrusb2-audio.c new file mode 100644 index 0000000000..13051f0e25 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-audio.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#include "pvrusb2-audio.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <linux/videodev2.h> +#include <media/drv-intf/msp3400.h> +#include <media/v4l2-common.h> + + +struct routing_scheme { + const int *def; + unsigned int cnt; +}; + +static const int routing_scheme0[] = { + [PVR2_CVAL_INPUT_TV] = MSP_INPUT_DEFAULT, + [PVR2_CVAL_INPUT_RADIO] = MSP_INPUT(MSP_IN_SCART2, + MSP_IN_TUNER1, + MSP_DSP_IN_SCART, + MSP_DSP_IN_SCART), + [PVR2_CVAL_INPUT_COMPOSITE] = MSP_INPUT(MSP_IN_SCART1, + MSP_IN_TUNER1, + MSP_DSP_IN_SCART, + MSP_DSP_IN_SCART), + [PVR2_CVAL_INPUT_SVIDEO] = MSP_INPUT(MSP_IN_SCART1, + MSP_IN_TUNER1, + MSP_DSP_IN_SCART, + MSP_DSP_IN_SCART), +}; + +static const struct routing_scheme routing_def0 = { + .def = routing_scheme0, + .cnt = ARRAY_SIZE(routing_scheme0), +}; + +static const struct routing_scheme *routing_schemes[] = { + [PVR2_ROUTING_SCHEME_HAUPPAUGE] = &routing_def0, +}; + +void pvr2_msp3400_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd) +{ + if (hdw->input_dirty || hdw->force_dirty) { + const struct routing_scheme *sp; + unsigned int sid = hdw->hdw_desc->signal_routing_scheme; + u32 input; + + pvr2_trace(PVR2_TRACE_CHIPS, "subdev msp3400 v4l2 set_stereo"); + sp = (sid < ARRAY_SIZE(routing_schemes)) ? + routing_schemes[sid] : NULL; + + if ((sp != NULL) && + (hdw->input_val >= 0) && + (hdw->input_val < sp->cnt)) { + input = sp->def[hdw->input_val]; + } else { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "*** WARNING *** subdev msp3400 set_input: Invalid routing scheme (%u) and/or input (%d)", + sid, hdw->input_val); + return; + } + sd->ops->audio->s_routing(sd, input, + MSP_OUTPUT(MSP_SC_IN_DSP_SCART1), 0); + } +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-audio.h b/drivers/media/usb/pvrusb2/pvrusb2-audio.h new file mode 100644 index 0000000000..87fb4c0747 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-audio.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#ifndef __PVRUSB2_AUDIO_H +#define __PVRUSB2_AUDIO_H + +#include "pvrusb2-hdw-internal.h" +void pvr2_msp3400_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *); +#endif /* __PVRUSB2_AUDIO_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-context.c b/drivers/media/usb/pvrusb2/pvrusb2-context.c new file mode 100644 index 0000000000..1764674de9 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-context.c @@ -0,0 +1,409 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ + +#include "pvrusb2-context.h" +#include "pvrusb2-io.h" +#include "pvrusb2-ioread.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-debug.h" +#include <linux/wait.h> +#include <linux/kthread.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> + +static struct pvr2_context *pvr2_context_exist_first; +static struct pvr2_context *pvr2_context_exist_last; +static struct pvr2_context *pvr2_context_notify_first; +static struct pvr2_context *pvr2_context_notify_last; +static DEFINE_MUTEX(pvr2_context_mutex); +static DECLARE_WAIT_QUEUE_HEAD(pvr2_context_sync_data); +static DECLARE_WAIT_QUEUE_HEAD(pvr2_context_cleanup_data); +static int pvr2_context_cleanup_flag; +static int pvr2_context_cleaned_flag; +static struct task_struct *pvr2_context_thread_ptr; + + +static void pvr2_context_set_notify(struct pvr2_context *mp, int fl) +{ + int signal_flag = 0; + mutex_lock(&pvr2_context_mutex); + if (fl) { + if (!mp->notify_flag) { + signal_flag = (pvr2_context_notify_first == NULL); + mp->notify_prev = pvr2_context_notify_last; + mp->notify_next = NULL; + pvr2_context_notify_last = mp; + if (mp->notify_prev) { + mp->notify_prev->notify_next = mp; + } else { + pvr2_context_notify_first = mp; + } + mp->notify_flag = !0; + } + } else { + if (mp->notify_flag) { + mp->notify_flag = 0; + if (mp->notify_next) { + mp->notify_next->notify_prev = mp->notify_prev; + } else { + pvr2_context_notify_last = mp->notify_prev; + } + if (mp->notify_prev) { + mp->notify_prev->notify_next = mp->notify_next; + } else { + pvr2_context_notify_first = mp->notify_next; + } + } + } + mutex_unlock(&pvr2_context_mutex); + if (signal_flag) wake_up(&pvr2_context_sync_data); +} + + +static void pvr2_context_destroy(struct pvr2_context *mp) +{ + pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context %p (destroy)",mp); + pvr2_hdw_destroy(mp->hdw); + pvr2_context_set_notify(mp, 0); + mutex_lock(&pvr2_context_mutex); + if (mp->exist_next) { + mp->exist_next->exist_prev = mp->exist_prev; + } else { + pvr2_context_exist_last = mp->exist_prev; + } + if (mp->exist_prev) { + mp->exist_prev->exist_next = mp->exist_next; + } else { + pvr2_context_exist_first = mp->exist_next; + } + if (!pvr2_context_exist_first) { + /* Trigger wakeup on control thread in case it is waiting + for an exit condition. */ + wake_up(&pvr2_context_sync_data); + } + mutex_unlock(&pvr2_context_mutex); + kfree(mp); +} + + +static void pvr2_context_notify(struct pvr2_context *mp) +{ + pvr2_context_set_notify(mp,!0); +} + + +static void pvr2_context_check(struct pvr2_context *mp) +{ + struct pvr2_channel *ch1, *ch2; + pvr2_trace(PVR2_TRACE_CTXT, + "pvr2_context %p (notify)", mp); + if (!mp->initialized_flag && !mp->disconnect_flag) { + mp->initialized_flag = !0; + pvr2_trace(PVR2_TRACE_CTXT, + "pvr2_context %p (initialize)", mp); + /* Finish hardware initialization */ + if (pvr2_hdw_initialize(mp->hdw, + (void (*)(void *))pvr2_context_notify, + mp)) { + mp->video_stream.stream = + pvr2_hdw_get_video_stream(mp->hdw); + /* Trigger interface initialization. By doing this + here initialization runs in our own safe and + cozy thread context. */ + if (mp->setup_func) mp->setup_func(mp); + } else { + pvr2_trace(PVR2_TRACE_CTXT, + "pvr2_context %p (thread skipping setup)", + mp); + /* Even though initialization did not succeed, + we're still going to continue anyway. We need + to do this in order to await the expected + disconnect (which we will detect in the normal + course of operation). */ + } + } + + for (ch1 = mp->mc_first; ch1; ch1 = ch2) { + ch2 = ch1->mc_next; + if (ch1->check_func) ch1->check_func(ch1); + } + + if (mp->disconnect_flag && !mp->mc_first) { + /* Go away... */ + pvr2_context_destroy(mp); + return; + } +} + + +static int pvr2_context_shutok(void) +{ + return pvr2_context_cleanup_flag && (pvr2_context_exist_first == NULL); +} + + +static int pvr2_context_thread_func(void *foo) +{ + struct pvr2_context *mp; + + pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread start"); + + do { + while ((mp = pvr2_context_notify_first) != NULL) { + pvr2_context_set_notify(mp, 0); + pvr2_context_check(mp); + } + wait_event_interruptible( + pvr2_context_sync_data, + ((pvr2_context_notify_first != NULL) || + pvr2_context_shutok())); + } while (!pvr2_context_shutok()); + + pvr2_context_cleaned_flag = !0; + wake_up(&pvr2_context_cleanup_data); + + pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread cleaned up"); + + wait_event_interruptible( + pvr2_context_sync_data, + kthread_should_stop()); + + pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread end"); + + return 0; +} + + +int pvr2_context_global_init(void) +{ + pvr2_context_thread_ptr = kthread_run(pvr2_context_thread_func, + NULL, + "pvrusb2-context"); + return IS_ERR(pvr2_context_thread_ptr) ? -ENOMEM : 0; +} + + +void pvr2_context_global_done(void) +{ + pvr2_context_cleanup_flag = !0; + wake_up(&pvr2_context_sync_data); + wait_event_interruptible( + pvr2_context_cleanup_data, + pvr2_context_cleaned_flag); + kthread_stop(pvr2_context_thread_ptr); +} + + +struct pvr2_context *pvr2_context_create( + struct usb_interface *intf, + const struct usb_device_id *devid, + void (*setup_func)(struct pvr2_context *)) +{ + struct pvr2_context *mp = NULL; + mp = kzalloc(sizeof(*mp),GFP_KERNEL); + if (!mp) goto done; + pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context %p (create)",mp); + mp->setup_func = setup_func; + mutex_init(&mp->mutex); + mutex_lock(&pvr2_context_mutex); + mp->exist_prev = pvr2_context_exist_last; + mp->exist_next = NULL; + pvr2_context_exist_last = mp; + if (mp->exist_prev) { + mp->exist_prev->exist_next = mp; + } else { + pvr2_context_exist_first = mp; + } + mutex_unlock(&pvr2_context_mutex); + mp->hdw = pvr2_hdw_create(intf,devid); + if (!mp->hdw) { + pvr2_context_destroy(mp); + mp = NULL; + goto done; + } + pvr2_context_set_notify(mp, !0); + done: + return mp; +} + + +static void pvr2_context_reset_input_limits(struct pvr2_context *mp) +{ + unsigned int tmsk,mmsk; + struct pvr2_channel *cp; + struct pvr2_hdw *hdw = mp->hdw; + mmsk = pvr2_hdw_get_input_available(hdw); + tmsk = mmsk; + for (cp = mp->mc_first; cp; cp = cp->mc_next) { + if (!cp->input_mask) continue; + tmsk &= cp->input_mask; + } + pvr2_hdw_set_input_allowed(hdw,mmsk,tmsk); + pvr2_hdw_commit_ctl(hdw); +} + + +static void pvr2_context_enter(struct pvr2_context *mp) +{ + mutex_lock(&mp->mutex); +} + + +static void pvr2_context_exit(struct pvr2_context *mp) +{ + int destroy_flag = 0; + if (!(mp->mc_first || !mp->disconnect_flag)) { + destroy_flag = !0; + } + mutex_unlock(&mp->mutex); + if (destroy_flag) pvr2_context_notify(mp); +} + + +void pvr2_context_disconnect(struct pvr2_context *mp) +{ + pvr2_hdw_disconnect(mp->hdw); + mp->disconnect_flag = !0; + if (!pvr2_context_shutok()) + pvr2_context_notify(mp); +} + + +void pvr2_channel_init(struct pvr2_channel *cp,struct pvr2_context *mp) +{ + pvr2_context_enter(mp); + cp->hdw = mp->hdw; + cp->mc_head = mp; + cp->mc_next = NULL; + cp->mc_prev = mp->mc_last; + if (mp->mc_last) { + mp->mc_last->mc_next = cp; + } else { + mp->mc_first = cp; + } + mp->mc_last = cp; + pvr2_context_exit(mp); +} + + +static void pvr2_channel_disclaim_stream(struct pvr2_channel *cp) +{ + if (!cp->stream) return; + pvr2_stream_kill(cp->stream->stream); + cp->stream->user = NULL; + cp->stream = NULL; +} + + +void pvr2_channel_done(struct pvr2_channel *cp) +{ + struct pvr2_context *mp = cp->mc_head; + pvr2_context_enter(mp); + cp->input_mask = 0; + pvr2_channel_disclaim_stream(cp); + pvr2_context_reset_input_limits(mp); + if (cp->mc_next) { + cp->mc_next->mc_prev = cp->mc_prev; + } else { + mp->mc_last = cp->mc_prev; + } + if (cp->mc_prev) { + cp->mc_prev->mc_next = cp->mc_next; + } else { + mp->mc_first = cp->mc_next; + } + cp->hdw = NULL; + pvr2_context_exit(mp); +} + + +int pvr2_channel_limit_inputs(struct pvr2_channel *cp,unsigned int cmsk) +{ + unsigned int tmsk,mmsk; + int ret = 0; + struct pvr2_channel *p2; + struct pvr2_hdw *hdw = cp->hdw; + + mmsk = pvr2_hdw_get_input_available(hdw); + cmsk &= mmsk; + if (cmsk == cp->input_mask) { + /* No change; nothing to do */ + return 0; + } + + pvr2_context_enter(cp->mc_head); + do { + if (!cmsk) { + cp->input_mask = 0; + pvr2_context_reset_input_limits(cp->mc_head); + break; + } + tmsk = mmsk; + for (p2 = cp->mc_head->mc_first; p2; p2 = p2->mc_next) { + if (p2 == cp) continue; + if (!p2->input_mask) continue; + tmsk &= p2->input_mask; + } + if (!(tmsk & cmsk)) { + ret = -EPERM; + break; + } + tmsk &= cmsk; + if ((ret = pvr2_hdw_set_input_allowed(hdw,mmsk,tmsk)) != 0) { + /* Internal failure changing allowed list; probably + should not happen, but react if it does. */ + break; + } + cp->input_mask = cmsk; + pvr2_hdw_commit_ctl(hdw); + } while (0); + pvr2_context_exit(cp->mc_head); + return ret; +} + + +unsigned int pvr2_channel_get_limited_inputs(struct pvr2_channel *cp) +{ + return cp->input_mask; +} + + +int pvr2_channel_claim_stream(struct pvr2_channel *cp, + struct pvr2_context_stream *sp) +{ + int code = 0; + pvr2_context_enter(cp->mc_head); do { + if (sp == cp->stream) break; + if (sp && sp->user) { + code = -EBUSY; + break; + } + pvr2_channel_disclaim_stream(cp); + if (!sp) break; + sp->user = cp; + cp->stream = sp; + } while (0); + pvr2_context_exit(cp->mc_head); + return code; +} + + +// This is the marker for the real beginning of a legitimate mpeg2 stream. +static char stream_sync_key[] = { + 0x00, 0x00, 0x01, 0xba, +}; + +struct pvr2_ioread *pvr2_channel_create_mpeg_stream( + struct pvr2_context_stream *sp) +{ + struct pvr2_ioread *cp; + cp = pvr2_ioread_create(); + if (!cp) return NULL; + pvr2_ioread_setup(cp,sp->stream); + pvr2_ioread_set_sync_key(cp,stream_sync_key,sizeof(stream_sync_key)); + return cp; +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-context.h b/drivers/media/usb/pvrusb2/pvrusb2-context.h new file mode 100644 index 0000000000..5840b2ce8f --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-context.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_CONTEXT_H +#define __PVRUSB2_CONTEXT_H + +#include <linux/mutex.h> +#include <linux/usb.h> +#include <linux/workqueue.h> + +struct pvr2_hdw; /* hardware interface - defined elsewhere */ +struct pvr2_stream; /* stream interface - defined elsewhere */ + +struct pvr2_context; /* All central state */ +struct pvr2_channel; /* One I/O pathway to a user */ +struct pvr2_context_stream; /* Wrapper for a stream */ +struct pvr2_ioread; /* Low level stream structure */ + +struct pvr2_context_stream { + struct pvr2_channel *user; + struct pvr2_stream *stream; +}; + +struct pvr2_context { + struct pvr2_channel *mc_first; + struct pvr2_channel *mc_last; + struct pvr2_context *exist_next; + struct pvr2_context *exist_prev; + struct pvr2_context *notify_next; + struct pvr2_context *notify_prev; + struct pvr2_hdw *hdw; + struct pvr2_context_stream video_stream; + struct mutex mutex; + int notify_flag; + int initialized_flag; + int disconnect_flag; + + /* Called after pvr2_context initialization is complete */ + void (*setup_func)(struct pvr2_context *); + +}; + +struct pvr2_channel { + struct pvr2_context *mc_head; + struct pvr2_channel *mc_next; + struct pvr2_channel *mc_prev; + struct pvr2_context_stream *stream; + struct pvr2_hdw *hdw; + unsigned int input_mask; + void (*check_func)(struct pvr2_channel *); +}; + +struct pvr2_context *pvr2_context_create(struct usb_interface *intf, + const struct usb_device_id *devid, + void (*setup_func)(struct pvr2_context *)); +void pvr2_context_disconnect(struct pvr2_context *); + +void pvr2_channel_init(struct pvr2_channel *,struct pvr2_context *); +void pvr2_channel_done(struct pvr2_channel *); +int pvr2_channel_limit_inputs(struct pvr2_channel *,unsigned int); +unsigned int pvr2_channel_get_limited_inputs(struct pvr2_channel *); +int pvr2_channel_claim_stream(struct pvr2_channel *, + struct pvr2_context_stream *); +struct pvr2_ioread *pvr2_channel_create_mpeg_stream( + struct pvr2_context_stream *); + +int pvr2_context_global_init(void); +void pvr2_context_global_done(void); + +#endif /* __PVRUSB2_CONTEXT_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.c b/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.c new file mode 100644 index 0000000000..7eefa0f86a --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +/* + + This source file is specifically designed to interface with the + v4l-dvb cs53l32a module. + +*/ + +#include "pvrusb2-cs53l32a.h" + + +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <linux/errno.h> + +struct routing_scheme { + const int *def; + unsigned int cnt; +}; + + +static const int routing_scheme1[] = { + [PVR2_CVAL_INPUT_TV] = 2, /* 1 or 2 seems to work here */ + [PVR2_CVAL_INPUT_RADIO] = 2, + [PVR2_CVAL_INPUT_COMPOSITE] = 0, + [PVR2_CVAL_INPUT_SVIDEO] = 0, +}; + +static const struct routing_scheme routing_def1 = { + .def = routing_scheme1, + .cnt = ARRAY_SIZE(routing_scheme1), +}; + +static const struct routing_scheme *routing_schemes[] = { + [PVR2_ROUTING_SCHEME_ONAIR] = &routing_def1, +}; + + +void pvr2_cs53l32a_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd) +{ + if (hdw->input_dirty || hdw->force_dirty) { + const struct routing_scheme *sp; + unsigned int sid = hdw->hdw_desc->signal_routing_scheme; + u32 input; + pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_input(%d)", + hdw->input_val); + sp = (sid < ARRAY_SIZE(routing_schemes)) ? + routing_schemes[sid] : NULL; + if ((sp == NULL) || + (hdw->input_val < 0) || + (hdw->input_val >= sp->cnt)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "*** WARNING *** subdev v4l2 set_input: Invalid routing scheme (%u) and/or input (%d)", + sid, hdw->input_val); + return; + } + input = sp->def[hdw->input_val]; + sd->ops->audio->s_routing(sd, input, 0, 0); + } +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.h b/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.h new file mode 100644 index 0000000000..800841ba83 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#ifndef __PVRUSB2_CS53L32A_H +#define __PVRUSB2_CS53L32A_H + +/* + + This module connects the pvrusb2 driver to the I2C chip level + driver which handles device video processing. This interface is + used internally by the driver; higher level code should only + interact through the interface provided by pvrusb2-hdw.h. + +*/ + + +#include "pvrusb2-hdw-internal.h" +void pvr2_cs53l32a_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *); + +#endif /* __PVRUSB2_AUDIO_CS53L32A_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-ctrl.c b/drivers/media/usb/pvrusb2/pvrusb2-ctrl.c new file mode 100644 index 0000000000..8ae3ad80cc --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-ctrl.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ + +#include "pvrusb2-ctrl.h" +#include "pvrusb2-hdw-internal.h" +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mutex.h> + + +static int pvr2_ctrl_range_check(struct pvr2_ctrl *cptr,int val) +{ + if (cptr->info->check_value) { + if (!cptr->info->check_value(cptr,val)) return -ERANGE; + } else if (cptr->info->type == pvr2_ctl_enum) { + if (val < 0) return -ERANGE; + if (val >= cptr->info->def.type_enum.count) return -ERANGE; + } else { + int lim; + lim = cptr->info->def.type_int.min_value; + if (cptr->info->get_min_value) { + cptr->info->get_min_value(cptr,&lim); + } + if (val < lim) return -ERANGE; + lim = cptr->info->def.type_int.max_value; + if (cptr->info->get_max_value) { + cptr->info->get_max_value(cptr,&lim); + } + if (val > lim) return -ERANGE; + } + return 0; +} + + +/* Set the given control. */ +int pvr2_ctrl_set_value(struct pvr2_ctrl *cptr,int val) +{ + return pvr2_ctrl_set_mask_value(cptr,~0,val); +} + + +/* Set/clear specific bits of the given control. */ +int pvr2_ctrl_set_mask_value(struct pvr2_ctrl *cptr,int mask,int val) +{ + int ret = 0; + if (!cptr) return -EINVAL; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->set_value) { + if (cptr->info->type == pvr2_ctl_bitmask) { + mask &= cptr->info->def.type_bitmask.valid_bits; + } else if ((cptr->info->type == pvr2_ctl_int)|| + (cptr->info->type == pvr2_ctl_enum)) { + ret = pvr2_ctrl_range_check(cptr,val); + if (ret < 0) break; + } else if (cptr->info->type != pvr2_ctl_bool) { + break; + } + ret = cptr->info->set_value(cptr,mask,val); + } else { + ret = -EPERM; + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Get the current value of the given control. */ +int pvr2_ctrl_get_value(struct pvr2_ctrl *cptr,int *valptr) +{ + int ret = 0; + if (!cptr) return -EINVAL; + LOCK_TAKE(cptr->hdw->big_lock); do { + ret = cptr->info->get_value(cptr,valptr); + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Retrieve control's type */ +enum pvr2_ctl_type pvr2_ctrl_get_type(struct pvr2_ctrl *cptr) +{ + if (!cptr) return pvr2_ctl_int; + return cptr->info->type; +} + + +/* Retrieve control's maximum value (int type) */ +int pvr2_ctrl_get_max(struct pvr2_ctrl *cptr) +{ + int ret = 0; + if (!cptr) return 0; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->get_max_value) { + cptr->info->get_max_value(cptr,&ret); + } else if (cptr->info->type == pvr2_ctl_int) { + ret = cptr->info->def.type_int.max_value; + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Retrieve control's minimum value (int type) */ +int pvr2_ctrl_get_min(struct pvr2_ctrl *cptr) +{ + int ret = 0; + if (!cptr) return 0; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->get_min_value) { + cptr->info->get_min_value(cptr,&ret); + } else if (cptr->info->type == pvr2_ctl_int) { + ret = cptr->info->def.type_int.min_value; + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Retrieve control's default value (any type) */ +int pvr2_ctrl_get_def(struct pvr2_ctrl *cptr, int *valptr) +{ + int ret = 0; + if (!cptr) return -EINVAL; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->get_def_value) { + ret = cptr->info->get_def_value(cptr, valptr); + } else { + *valptr = cptr->info->default_value; + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Retrieve control's enumeration count (enum only) */ +int pvr2_ctrl_get_cnt(struct pvr2_ctrl *cptr) +{ + int ret = 0; + if (!cptr) return 0; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->type == pvr2_ctl_enum) { + ret = cptr->info->def.type_enum.count; + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Retrieve control's valid mask bits (bit mask only) */ +int pvr2_ctrl_get_mask(struct pvr2_ctrl *cptr) +{ + int ret = 0; + if (!cptr) return 0; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->type == pvr2_ctl_bitmask) { + ret = cptr->info->def.type_bitmask.valid_bits; + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Retrieve the control's name */ +const char *pvr2_ctrl_get_name(struct pvr2_ctrl *cptr) +{ + if (!cptr) return NULL; + return cptr->info->name; +} + + +/* Retrieve the control's desc */ +const char *pvr2_ctrl_get_desc(struct pvr2_ctrl *cptr) +{ + if (!cptr) return NULL; + return cptr->info->desc; +} + + +/* Retrieve a control enumeration or bit mask value */ +int pvr2_ctrl_get_valname(struct pvr2_ctrl *cptr,int val, + char *bptr,unsigned int bmax, + unsigned int *blen) +{ + int ret = -EINVAL; + if (!cptr) return 0; + *blen = 0; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->type == pvr2_ctl_enum) { + const char * const *names; + names = cptr->info->def.type_enum.value_names; + if (pvr2_ctrl_range_check(cptr,val) == 0) { + if (names[val]) { + *blen = scnprintf( + bptr,bmax,"%s", + names[val]); + } else { + *blen = 0; + } + ret = 0; + } + } else if (cptr->info->type == pvr2_ctl_bitmask) { + const char **names; + unsigned int idx; + int msk; + names = cptr->info->def.type_bitmask.bit_names; + val &= cptr->info->def.type_bitmask.valid_bits; + for (idx = 0, msk = 1; val; idx++, msk <<= 1) { + if (val & msk) { + *blen = scnprintf(bptr,bmax,"%s", + names[idx]); + ret = 0; + break; + } + } + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Return V4L ID for this control or zero if none */ +int pvr2_ctrl_get_v4lid(struct pvr2_ctrl *cptr) +{ + if (!cptr) return 0; + return cptr->info->v4l_id; +} + + +unsigned int pvr2_ctrl_get_v4lflags(struct pvr2_ctrl *cptr) +{ + unsigned int flags = 0; + + if (cptr->info->get_v4lflags) { + flags = cptr->info->get_v4lflags(cptr); + } + + if (cptr->info->set_value) { + flags &= ~V4L2_CTRL_FLAG_READ_ONLY; + } else { + flags |= V4L2_CTRL_FLAG_READ_ONLY; + } + + return flags; +} + + +/* Return true if control is writable */ +int pvr2_ctrl_is_writable(struct pvr2_ctrl *cptr) +{ + if (!cptr) return 0; + return cptr->info->set_value != NULL; +} + + +/* Return true if control has custom symbolic representation */ +int pvr2_ctrl_has_custom_symbols(struct pvr2_ctrl *cptr) +{ + if (!cptr) return 0; + if (!cptr->info->val_to_sym) return 0; + if (!cptr->info->sym_to_val) return 0; + return !0; +} + + +/* Convert a given mask/val to a custom symbolic value */ +int pvr2_ctrl_custom_value_to_sym(struct pvr2_ctrl *cptr, + int mask,int val, + char *buf,unsigned int maxlen, + unsigned int *len) +{ + if (!cptr) return -EINVAL; + if (!cptr->info->val_to_sym) return -EINVAL; + return cptr->info->val_to_sym(cptr,mask,val,buf,maxlen,len); +} + + +/* Convert a symbolic value to a mask/value pair */ +int pvr2_ctrl_custom_sym_to_value(struct pvr2_ctrl *cptr, + const char *buf,unsigned int len, + int *maskptr,int *valptr) +{ + if (!cptr) return -EINVAL; + if (!cptr->info->sym_to_val) return -EINVAL; + return cptr->info->sym_to_val(cptr,buf,len,maskptr,valptr); +} + + +static unsigned int gen_bitmask_string(int msk,int val,int msk_only, + const char **names, + char *ptr,unsigned int len) +{ + unsigned int idx; + long sm,um; + int spcFl; + unsigned int uc,cnt; + const char *idStr; + + spcFl = 0; + uc = 0; + um = 0; + for (idx = 0, sm = 1; msk; idx++, sm <<= 1) { + if (sm & msk) { + msk &= ~sm; + idStr = names[idx]; + if (idStr) { + cnt = scnprintf(ptr,len,"%s%s%s", + (spcFl ? " " : ""), + (msk_only ? "" : + ((val & sm) ? "+" : "-")), + idStr); + ptr += cnt; len -= cnt; uc += cnt; + spcFl = !0; + } else { + um |= sm; + } + } + } + if (um) { + if (msk_only) { + cnt = scnprintf(ptr,len,"%s0x%lx", + (spcFl ? " " : ""), + um); + ptr += cnt; len -= cnt; uc += cnt; + spcFl = !0; + } else if (um & val) { + cnt = scnprintf(ptr,len,"%s+0x%lx", + (spcFl ? " " : ""), + um & val); + ptr += cnt; len -= cnt; uc += cnt; + spcFl = !0; + } else if (um & ~val) { + cnt = scnprintf(ptr,len,"%s+0x%lx", + (spcFl ? " " : ""), + um & ~val); + ptr += cnt; len -= cnt; uc += cnt; + spcFl = !0; + } + } + return uc; +} + + +static const char *boolNames[] = { + "false", + "true", + "no", + "yes", +}; + + +static int parse_token(const char *ptr,unsigned int len, + int *valptr, + const char * const *names, unsigned int namecnt) +{ + unsigned int slen; + unsigned int idx; + *valptr = 0; + if (!names) namecnt = 0; + for (idx = 0; idx < namecnt; idx++) { + if (!names[idx]) continue; + slen = strlen(names[idx]); + if (slen != len) continue; + if (memcmp(names[idx],ptr,slen)) continue; + *valptr = idx; + return 0; + } + return kstrtoint(ptr, 0, valptr) ? -EINVAL : 1; +} + + +static int parse_mtoken(const char *ptr,unsigned int len, + int *valptr, + const char **names,int valid_bits) +{ + unsigned int slen; + unsigned int idx; + int msk; + *valptr = 0; + for (idx = 0, msk = 1; valid_bits; idx++, msk <<= 1) { + if (!(msk & valid_bits)) continue; + valid_bits &= ~msk; + if (!names[idx]) continue; + slen = strlen(names[idx]); + if (slen != len) continue; + if (memcmp(names[idx],ptr,slen)) continue; + *valptr = msk; + return 0; + } + return kstrtoint(ptr, 0, valptr); +} + + +static int parse_tlist(const char *ptr,unsigned int len, + int *maskptr,int *valptr, + const char **names,int valid_bits) +{ + unsigned int cnt; + int mask,val,kv,mode,ret; + mask = 0; + val = 0; + ret = 0; + while (len) { + cnt = 0; + while ((cnt < len) && + ((ptr[cnt] <= 32) || + (ptr[cnt] >= 127))) cnt++; + ptr += cnt; + len -= cnt; + mode = 0; + if ((*ptr == '-') || (*ptr == '+')) { + mode = (*ptr == '-') ? -1 : 1; + ptr++; + len--; + } + cnt = 0; + while (cnt < len) { + if (ptr[cnt] <= 32) break; + if (ptr[cnt] >= 127) break; + cnt++; + } + if (!cnt) break; + if (parse_mtoken(ptr,cnt,&kv,names,valid_bits)) { + ret = -EINVAL; + break; + } + ptr += cnt; + len -= cnt; + switch (mode) { + case 0: + mask = valid_bits; + val |= kv; + break; + case -1: + mask |= kv; + val &= ~kv; + break; + case 1: + mask |= kv; + val |= kv; + break; + default: + break; + } + } + *maskptr = mask; + *valptr = val; + return ret; +} + + +/* Convert a symbolic value to a mask/value pair */ +int pvr2_ctrl_sym_to_value(struct pvr2_ctrl *cptr, + const char *ptr,unsigned int len, + int *maskptr,int *valptr) +{ + int ret = -EINVAL; + unsigned int cnt; + + *maskptr = 0; + *valptr = 0; + + cnt = 0; + while ((cnt < len) && ((ptr[cnt] <= 32) || (ptr[cnt] >= 127))) cnt++; + len -= cnt; ptr += cnt; + cnt = 0; + while ((cnt < len) && ((ptr[len-(cnt+1)] <= 32) || + (ptr[len-(cnt+1)] >= 127))) cnt++; + len -= cnt; + + if (!len) return -EINVAL; + + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->type == pvr2_ctl_int) { + ret = parse_token(ptr,len,valptr,NULL,0); + if (ret >= 0) { + ret = pvr2_ctrl_range_check(cptr,*valptr); + } + *maskptr = ~0; + } else if (cptr->info->type == pvr2_ctl_bool) { + ret = parse_token(ptr,len,valptr,boolNames, + ARRAY_SIZE(boolNames)); + if (ret == 1) { + *valptr = *valptr ? !0 : 0; + } else if (ret == 0) { + *valptr = (*valptr & 1) ? !0 : 0; + } + *maskptr = 1; + } else if (cptr->info->type == pvr2_ctl_enum) { + ret = parse_token( + ptr,len,valptr, + cptr->info->def.type_enum.value_names, + cptr->info->def.type_enum.count); + if (ret >= 0) { + ret = pvr2_ctrl_range_check(cptr,*valptr); + } + *maskptr = ~0; + } else if (cptr->info->type == pvr2_ctl_bitmask) { + ret = parse_tlist( + ptr,len,maskptr,valptr, + cptr->info->def.type_bitmask.bit_names, + cptr->info->def.type_bitmask.valid_bits); + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Convert a given mask/val to a symbolic value */ +int pvr2_ctrl_value_to_sym_internal(struct pvr2_ctrl *cptr, + int mask,int val, + char *buf,unsigned int maxlen, + unsigned int *len) +{ + int ret = -EINVAL; + + *len = 0; + if (cptr->info->type == pvr2_ctl_int) { + *len = scnprintf(buf,maxlen,"%d",val); + ret = 0; + } else if (cptr->info->type == pvr2_ctl_bool) { + *len = scnprintf(buf,maxlen,"%s",val ? "true" : "false"); + ret = 0; + } else if (cptr->info->type == pvr2_ctl_enum) { + const char * const *names; + names = cptr->info->def.type_enum.value_names; + if ((val >= 0) && + (val < cptr->info->def.type_enum.count)) { + if (names[val]) { + *len = scnprintf( + buf,maxlen,"%s", + names[val]); + } else { + *len = 0; + } + ret = 0; + } + } else if (cptr->info->type == pvr2_ctl_bitmask) { + *len = gen_bitmask_string( + val & mask & cptr->info->def.type_bitmask.valid_bits, + ~0,!0, + cptr->info->def.type_bitmask.bit_names, + buf,maxlen); + } + return ret; +} + + +/* Convert a given mask/val to a symbolic value */ +int pvr2_ctrl_value_to_sym(struct pvr2_ctrl *cptr, + int mask,int val, + char *buf,unsigned int maxlen, + unsigned int *len) +{ + int ret; + LOCK_TAKE(cptr->hdw->big_lock); do { + ret = pvr2_ctrl_value_to_sym_internal(cptr,mask,val, + buf,maxlen,len); + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-ctrl.h b/drivers/media/usb/pvrusb2/pvrusb2-ctrl.h new file mode 100644 index 0000000000..7dd4b73f78 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-ctrl.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_CTRL_H +#define __PVRUSB2_CTRL_H + +struct pvr2_ctrl; + +enum pvr2_ctl_type { + pvr2_ctl_int = 0, + pvr2_ctl_enum = 1, + pvr2_ctl_bitmask = 2, + pvr2_ctl_bool = 3, +}; + + +/* Set the given control. */ +int pvr2_ctrl_set_value(struct pvr2_ctrl *,int val); + +/* Set/clear specific bits of the given control. */ +int pvr2_ctrl_set_mask_value(struct pvr2_ctrl *,int mask,int val); + +/* Get the current value of the given control. */ +int pvr2_ctrl_get_value(struct pvr2_ctrl *,int *valptr); + +/* Retrieve control's type */ +enum pvr2_ctl_type pvr2_ctrl_get_type(struct pvr2_ctrl *); + +/* Retrieve control's maximum value (int type) */ +int pvr2_ctrl_get_max(struct pvr2_ctrl *); + +/* Retrieve control's minimum value (int type) */ +int pvr2_ctrl_get_min(struct pvr2_ctrl *); + +/* Retrieve control's default value (any type) */ +int pvr2_ctrl_get_def(struct pvr2_ctrl *, int *valptr); + +/* Retrieve control's enumeration count (enum only) */ +int pvr2_ctrl_get_cnt(struct pvr2_ctrl *); + +/* Retrieve control's valid mask bits (bit mask only) */ +int pvr2_ctrl_get_mask(struct pvr2_ctrl *); + +/* Retrieve the control's name */ +const char *pvr2_ctrl_get_name(struct pvr2_ctrl *); + +/* Retrieve the control's desc */ +const char *pvr2_ctrl_get_desc(struct pvr2_ctrl *); + +/* Retrieve a control enumeration or bit mask value */ +int pvr2_ctrl_get_valname(struct pvr2_ctrl *,int,char *,unsigned int, + unsigned int *); + +/* Return true if control is writable */ +int pvr2_ctrl_is_writable(struct pvr2_ctrl *); + +/* Return V4L flags value for control (or zero if there is no v4l control + actually under this control) */ +unsigned int pvr2_ctrl_get_v4lflags(struct pvr2_ctrl *); + +/* Return V4L ID for this control or zero if none */ +int pvr2_ctrl_get_v4lid(struct pvr2_ctrl *); + +/* Return true if control has custom symbolic representation */ +int pvr2_ctrl_has_custom_symbols(struct pvr2_ctrl *); + +/* Convert a given mask/val to a custom symbolic value */ +int pvr2_ctrl_custom_value_to_sym(struct pvr2_ctrl *, + int mask,int val, + char *buf,unsigned int maxlen, + unsigned int *len); + +/* Convert a symbolic value to a mask/value pair */ +int pvr2_ctrl_custom_sym_to_value(struct pvr2_ctrl *, + const char *buf,unsigned int len, + int *maskptr,int *valptr); + +/* Convert a given mask/val to a symbolic value */ +int pvr2_ctrl_value_to_sym(struct pvr2_ctrl *, + int mask,int val, + char *buf,unsigned int maxlen, + unsigned int *len); + +/* Convert a symbolic value to a mask/value pair */ +int pvr2_ctrl_sym_to_value(struct pvr2_ctrl *, + const char *buf,unsigned int len, + int *maskptr,int *valptr); + +/* Convert a given mask/val to a symbolic value - must already be + inside of critical region. */ +int pvr2_ctrl_value_to_sym_internal(struct pvr2_ctrl *, + int mask,int val, + char *buf,unsigned int maxlen, + unsigned int *len); + +#endif /* __PVRUSB2_CTRL_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c new file mode 100644 index 0000000000..e4b31ae02f --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +/* + + This source file is specifically designed to interface with the + cx2584x, in kernels 2.6.16 or newer. + +*/ + +#include "pvrusb2-cx2584x-v4l.h" + + +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <media/drv-intf/cx25840.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <linux/errno.h> + + +struct routing_scheme_item { + int vid; + int aud; +}; + +struct routing_scheme { + const struct routing_scheme_item *def; + unsigned int cnt; +}; + +static const struct routing_scheme_item routing_scheme0[] = { + [PVR2_CVAL_INPUT_TV] = { + .vid = CX25840_COMPOSITE7, + .aud = CX25840_AUDIO8, + }, + [PVR2_CVAL_INPUT_RADIO] = { /* Treat the same as composite */ + .vid = CX25840_COMPOSITE3, + .aud = CX25840_AUDIO_SERIAL, + }, + [PVR2_CVAL_INPUT_COMPOSITE] = { + .vid = CX25840_COMPOSITE3, + .aud = CX25840_AUDIO_SERIAL, + }, + [PVR2_CVAL_INPUT_SVIDEO] = { + .vid = CX25840_SVIDEO1, + .aud = CX25840_AUDIO_SERIAL, + }, +}; + +static const struct routing_scheme routing_def0 = { + .def = routing_scheme0, + .cnt = ARRAY_SIZE(routing_scheme0), +}; + +/* Specific to gotview device */ +static const struct routing_scheme_item routing_schemegv[] = { + [PVR2_CVAL_INPUT_TV] = { + .vid = CX25840_COMPOSITE2, + .aud = CX25840_AUDIO5, + }, + [PVR2_CVAL_INPUT_RADIO] = { + /* line-in is used for radio and composite. A GPIO is + used to switch between the two choices. */ + .vid = CX25840_COMPOSITE1, + .aud = CX25840_AUDIO_SERIAL, + }, + [PVR2_CVAL_INPUT_COMPOSITE] = { + .vid = CX25840_COMPOSITE1, + .aud = CX25840_AUDIO_SERIAL, + }, + [PVR2_CVAL_INPUT_SVIDEO] = { + .vid = (CX25840_SVIDEO_LUMA3|CX25840_SVIDEO_CHROMA4), + .aud = CX25840_AUDIO_SERIAL, + }, +}; + +static const struct routing_scheme routing_defgv = { + .def = routing_schemegv, + .cnt = ARRAY_SIZE(routing_schemegv), +}; + +/* Specific to grabster av400 device */ +static const struct routing_scheme_item routing_schemeav400[] = { + [PVR2_CVAL_INPUT_COMPOSITE] = { + .vid = CX25840_COMPOSITE1, + .aud = CX25840_AUDIO_SERIAL, + }, + [PVR2_CVAL_INPUT_SVIDEO] = { + .vid = (CX25840_SVIDEO_LUMA2|CX25840_SVIDEO_CHROMA4), + .aud = CX25840_AUDIO_SERIAL, + }, +}; + +static const struct routing_scheme routing_defav400 = { + .def = routing_schemeav400, + .cnt = ARRAY_SIZE(routing_schemeav400), +}; + +static const struct routing_scheme_item routing_scheme160xxx[] = { + [PVR2_CVAL_INPUT_TV] = { + .vid = CX25840_COMPOSITE7, + .aud = CX25840_AUDIO8, + }, + [PVR2_CVAL_INPUT_RADIO] = { + .vid = CX25840_COMPOSITE4, + .aud = CX25840_AUDIO6, + }, + [PVR2_CVAL_INPUT_COMPOSITE] = { + .vid = CX25840_COMPOSITE3, + .aud = CX25840_AUDIO_SERIAL, + }, + [PVR2_CVAL_INPUT_SVIDEO] = { + .vid = CX25840_SVIDEO1, + .aud = CX25840_AUDIO_SERIAL, + }, +}; + +static const struct routing_scheme routing_def160xxx = { + .def = routing_scheme160xxx, + .cnt = ARRAY_SIZE(routing_scheme160xxx), +}; + +static const struct routing_scheme *routing_schemes[] = { + [PVR2_ROUTING_SCHEME_HAUPPAUGE] = &routing_def0, + [PVR2_ROUTING_SCHEME_GOTVIEW] = &routing_defgv, + [PVR2_ROUTING_SCHEME_AV400] = &routing_defav400, + [PVR2_ROUTING_SCHEME_HAUP160XXX] = &routing_def160xxx, +}; + +void pvr2_cx25840_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd) +{ + pvr2_trace(PVR2_TRACE_CHIPS, "subdev cx2584x update..."); + if (hdw->input_dirty || hdw->force_dirty) { + enum cx25840_video_input vid_input; + enum cx25840_audio_input aud_input; + const struct routing_scheme *sp; + unsigned int sid = hdw->hdw_desc->signal_routing_scheme; + + sp = (sid < ARRAY_SIZE(routing_schemes)) ? + routing_schemes[sid] : NULL; + if ((sp == NULL) || + (hdw->input_val < 0) || + (hdw->input_val >= sp->cnt)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "*** WARNING *** subdev cx2584x set_input: Invalid routing scheme (%u) and/or input (%d)", + sid, hdw->input_val); + return; + } + vid_input = sp->def[hdw->input_val].vid; + aud_input = sp->def[hdw->input_val].aud; + pvr2_trace(PVR2_TRACE_CHIPS, + "subdev cx2584x set_input vid=0x%x aud=0x%x", + vid_input, aud_input); + sd->ops->video->s_routing(sd, (u32)vid_input, 0, 0); + sd->ops->audio->s_routing(sd, (u32)aud_input, 0, 0); + } +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.h b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.h new file mode 100644 index 0000000000..57c9504793 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#ifndef __PVRUSB2_CX2584X_V4L_H +#define __PVRUSB2_CX2584X_V4L_H + +/* + + This module connects the pvrusb2 driver to the I2C chip level + driver which handles combined device audio & video processing. + This interface is used internally by the driver; higher level code + should only interact through the interface provided by + pvrusb2-hdw.h. + +*/ + + + +#include "pvrusb2-hdw-internal.h" + +void pvr2_cx25840_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *sd); + + +#endif /* __PVRUSB2_CX2584X_V4L_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-debug.h b/drivers/media/usb/pvrusb2/pvrusb2-debug.h new file mode 100644 index 0000000000..7854c1d87a --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-debug.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_DEBUG_H +#define __PVRUSB2_DEBUG_H + +extern int pvrusb2_debug; + +#define pvr2_trace(msk, fmt, arg...) do {if (msk & pvrusb2_debug) pr_info("pvrusb2: " fmt "\n", ##arg); } while (0) + +/* These are listed in *rough* order of decreasing usefulness and + increasing noise level. */ +#define PVR2_TRACE_INFO (1 << 0) /* Normal messages */ +#define PVR2_TRACE_ERROR_LEGS (1 << 1) /* error messages */ +#define PVR2_TRACE_TOLERANCE (1 << 2) /* track tolerance-affected errors */ +#define PVR2_TRACE_TRAP (1 << 3) /* Trap & report app misbehavior */ +#define PVR2_TRACE_STD (1 << 4) /* Log video standard stuff */ +#define PVR2_TRACE_INIT (1 << 5) /* misc initialization steps */ +#define PVR2_TRACE_START_STOP (1 << 6) /* Streaming start / stop */ +#define PVR2_TRACE_CTL (1 << 7) /* commit of control changes */ +#define PVR2_TRACE_STATE (1 << 8) /* Device state changes */ +#define PVR2_TRACE_STBITS (1 << 9) /* Individual bit state changes */ +#define PVR2_TRACE_EEPROM (1 << 10) /* eeprom parsing / report */ +#define PVR2_TRACE_STRUCT (1 << 11) /* internal struct creation */ +#define PVR2_TRACE_OPEN_CLOSE (1 << 12) /* application open / close */ +#define PVR2_TRACE_CTXT (1 << 13) /* Main context tracking */ +#define PVR2_TRACE_SYSFS (1 << 14) /* Sysfs driven I/O */ +#define PVR2_TRACE_FIRMWARE (1 << 15) /* firmware upload actions */ +#define PVR2_TRACE_CHIPS (1 << 16) /* chip broadcast operation */ +#define PVR2_TRACE_I2C (1 << 17) /* I2C related stuff */ +#define PVR2_TRACE_I2C_CMD (1 << 18) /* Software commands to I2C modules */ +#define PVR2_TRACE_I2C_CORE (1 << 19) /* I2C core debugging */ +#define PVR2_TRACE_I2C_TRAF (1 << 20) /* I2C traffic through the adapter */ +#define PVR2_TRACE_V4LIOCTL (1 << 21) /* v4l ioctl details */ +#define PVR2_TRACE_ENCODER (1 << 22) /* mpeg2 encoder operation */ +#define PVR2_TRACE_BUF_POOL (1 << 23) /* Track buffer pool management */ +#define PVR2_TRACE_BUF_FLOW (1 << 24) /* Track buffer flow in system */ +#define PVR2_TRACE_DATA_FLOW (1 << 25) /* Track data flow */ +#define PVR2_TRACE_DEBUGIFC (1 << 26) /* Debug interface actions */ +#define PVR2_TRACE_GPIO (1 << 27) /* GPIO state bit changes */ +#define PVR2_TRACE_DVB_FEED (1 << 28) /* DVB transport feed debug */ + + +#endif /* __PVRUSB2_HDW_INTERNAL_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-debugifc.c b/drivers/media/usb/pvrusb2/pvrusb2-debugifc.c new file mode 100644 index 0000000000..84cfb5ce8b --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-debugifc.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ + +#include <linux/string.h> +#include "pvrusb2-debugifc.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-debug.h" + +struct debugifc_mask_item { + const char *name; + unsigned long msk; +}; + + +static unsigned int debugifc_count_whitespace(const char *buf, + unsigned int count) +{ + unsigned int scnt; + char ch; + + for (scnt = 0; scnt < count; scnt++) { + ch = buf[scnt]; + if (ch == ' ') continue; + if (ch == '\t') continue; + if (ch == '\n') continue; + break; + } + return scnt; +} + + +static unsigned int debugifc_count_nonwhitespace(const char *buf, + unsigned int count) +{ + unsigned int scnt; + char ch; + + for (scnt = 0; scnt < count; scnt++) { + ch = buf[scnt]; + if (ch == ' ') break; + if (ch == '\t') break; + if (ch == '\n') break; + } + return scnt; +} + + +static unsigned int debugifc_isolate_word(const char *buf,unsigned int count, + const char **wstrPtr, + unsigned int *wlenPtr) +{ + const char *wptr; + unsigned int consume_cnt = 0; + unsigned int wlen; + unsigned int scnt; + + wptr = NULL; + wlen = 0; + scnt = debugifc_count_whitespace(buf,count); + consume_cnt += scnt; count -= scnt; buf += scnt; + if (!count) goto done; + + scnt = debugifc_count_nonwhitespace(buf,count); + if (!scnt) goto done; + wptr = buf; + wlen = scnt; + consume_cnt += scnt; count -= scnt; buf += scnt; + + done: + *wstrPtr = wptr; + *wlenPtr = wlen; + return consume_cnt; +} + + +static int debugifc_parse_unsigned_number(const char *buf,unsigned int count, + u32 *num_ptr) +{ + u32 result = 0; + int radix = 10; + if ((count >= 2) && (buf[0] == '0') && + ((buf[1] == 'x') || (buf[1] == 'X'))) { + radix = 16; + count -= 2; + buf += 2; + } else if ((count >= 1) && (buf[0] == '0')) { + radix = 8; + } + + while (count--) { + int val = hex_to_bin(*buf++); + if (val < 0 || val >= radix) + return -EINVAL; + result *= radix; + result += val; + } + *num_ptr = result; + return 0; +} + + +static int debugifc_match_keyword(const char *buf,unsigned int count, + const char *keyword) +{ + unsigned int kl; + if (!keyword) return 0; + kl = strlen(keyword); + if (kl != count) return 0; + return !memcmp(buf,keyword,kl); +} + + +int pvr2_debugifc_print_info(struct pvr2_hdw *hdw,char *buf,unsigned int acnt) +{ + int bcnt = 0; + int ccnt; + ccnt = scnprintf(buf, acnt, "Driver hardware description: %s\n", + pvr2_hdw_get_desc(hdw)); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = scnprintf(buf,acnt,"Driver state info:\n"); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = pvr2_hdw_state_report(hdw,buf,acnt); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + + return bcnt; +} + + +int pvr2_debugifc_print_status(struct pvr2_hdw *hdw, + char *buf,unsigned int acnt) +{ + int bcnt = 0; + int ccnt; + int ret; + u32 gpio_dir,gpio_in,gpio_out; + struct pvr2_stream_stats stats; + struct pvr2_stream *sp; + + ret = pvr2_hdw_is_hsm(hdw); + ccnt = scnprintf(buf,acnt,"USB link speed: %s\n", + (ret < 0 ? "FAIL" : (ret ? "high" : "full"))); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + + gpio_dir = 0; gpio_in = 0; gpio_out = 0; + pvr2_hdw_gpio_get_dir(hdw,&gpio_dir); + pvr2_hdw_gpio_get_out(hdw,&gpio_out); + pvr2_hdw_gpio_get_in(hdw,&gpio_in); + ccnt = scnprintf(buf,acnt,"GPIO state: dir=0x%x in=0x%x out=0x%x\n", + gpio_dir,gpio_in,gpio_out); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + + ccnt = scnprintf(buf,acnt,"Streaming is %s\n", + pvr2_hdw_get_streaming(hdw) ? "on" : "off"); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + + + sp = pvr2_hdw_get_video_stream(hdw); + if (sp) { + pvr2_stream_get_stats(sp, &stats, 0); + ccnt = scnprintf( + buf,acnt, + "Bytes streamed=%u URBs: queued=%u idle=%u ready=%u processed=%u failed=%u\n", + stats.bytes_processed, + stats.buffers_in_queue, + stats.buffers_in_idle, + stats.buffers_in_ready, + stats.buffers_processed, + stats.buffers_failed); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + } + + return bcnt; +} + + +static int pvr2_debugifc_do1cmd(struct pvr2_hdw *hdw,const char *buf, + unsigned int count) +{ + const char *wptr; + unsigned int wlen; + unsigned int scnt; + + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (!scnt) return 0; + count -= scnt; buf += scnt; + if (!wptr) return 0; + + pvr2_trace(PVR2_TRACE_DEBUGIFC,"debugifc cmd: \"%.*s\"",wlen,wptr); + if (debugifc_match_keyword(wptr,wlen,"reset")) { + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (!scnt) return -EINVAL; + count -= scnt; buf += scnt; + if (!wptr) return -EINVAL; + if (debugifc_match_keyword(wptr,wlen,"cpu")) { + pvr2_hdw_cpureset_assert(hdw,!0); + pvr2_hdw_cpureset_assert(hdw,0); + return 0; + } else if (debugifc_match_keyword(wptr,wlen,"bus")) { + pvr2_hdw_device_reset(hdw); + } else if (debugifc_match_keyword(wptr,wlen,"soft")) { + return pvr2_hdw_cmd_powerup(hdw); + } else if (debugifc_match_keyword(wptr,wlen,"deep")) { + return pvr2_hdw_cmd_deep_reset(hdw); + } else if (debugifc_match_keyword(wptr,wlen,"firmware")) { + return pvr2_upload_firmware2(hdw); + } else if (debugifc_match_keyword(wptr,wlen,"decoder")) { + return pvr2_hdw_cmd_decoder_reset(hdw); + } else if (debugifc_match_keyword(wptr,wlen,"worker")) { + return pvr2_hdw_untrip(hdw); + } else if (debugifc_match_keyword(wptr,wlen,"usbstats")) { + pvr2_stream_get_stats(pvr2_hdw_get_video_stream(hdw), + NULL, !0); + return 0; + } + return -EINVAL; + } else if (debugifc_match_keyword(wptr,wlen,"cpufw")) { + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (!scnt) return -EINVAL; + count -= scnt; buf += scnt; + if (!wptr) return -EINVAL; + if (debugifc_match_keyword(wptr,wlen,"fetch")) { + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (scnt && wptr) { + count -= scnt; buf += scnt; + if (debugifc_match_keyword(wptr, wlen, + "prom")) { + pvr2_hdw_cpufw_set_enabled(hdw, 2, !0); + } else if (debugifc_match_keyword(wptr, wlen, + "ram8k")) { + pvr2_hdw_cpufw_set_enabled(hdw, 0, !0); + } else if (debugifc_match_keyword(wptr, wlen, + "ram16k")) { + pvr2_hdw_cpufw_set_enabled(hdw, 1, !0); + } else { + return -EINVAL; + } + } + pvr2_hdw_cpufw_set_enabled(hdw,0,!0); + return 0; + } else if (debugifc_match_keyword(wptr,wlen,"done")) { + pvr2_hdw_cpufw_set_enabled(hdw,0,0); + return 0; + } else { + return -EINVAL; + } + } else if (debugifc_match_keyword(wptr,wlen,"gpio")) { + int dir_fl = 0; + int ret; + u32 msk,val; + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (!scnt) return -EINVAL; + count -= scnt; buf += scnt; + if (!wptr) return -EINVAL; + if (debugifc_match_keyword(wptr,wlen,"dir")) { + dir_fl = !0; + } else if (!debugifc_match_keyword(wptr,wlen,"out")) { + return -EINVAL; + } + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (!scnt) return -EINVAL; + count -= scnt; buf += scnt; + if (!wptr) return -EINVAL; + ret = debugifc_parse_unsigned_number(wptr,wlen,&msk); + if (ret) return ret; + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (wptr) { + ret = debugifc_parse_unsigned_number(wptr,wlen,&val); + if (ret) return ret; + } else { + val = msk; + msk = 0xffffffff; + } + if (dir_fl) { + ret = pvr2_hdw_gpio_chg_dir(hdw,msk,val); + } else { + ret = pvr2_hdw_gpio_chg_out(hdw,msk,val); + } + return ret; + } + pvr2_trace(PVR2_TRACE_DEBUGIFC, + "debugifc failed to recognize cmd: \"%.*s\"",wlen,wptr); + return -EINVAL; +} + + +int pvr2_debugifc_docmd(struct pvr2_hdw *hdw,const char *buf, + unsigned int count) +{ + unsigned int bcnt = 0; + int ret; + + while (count) { + for (bcnt = 0; bcnt < count; bcnt++) { + if (buf[bcnt] == '\n') break; + } + + ret = pvr2_debugifc_do1cmd(hdw,buf,bcnt); + if (ret < 0) return ret; + if (bcnt < count) bcnt++; + buf += bcnt; + count -= bcnt; + } + + return 0; +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-debugifc.h b/drivers/media/usb/pvrusb2/pvrusb2-debugifc.h new file mode 100644 index 0000000000..9b9c0f5534 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-debugifc.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_DEBUGIFC_H +#define __PVRUSB2_DEBUGIFC_H + +struct pvr2_hdw; + +/* Print general status of driver. This will also trigger a probe of + the USB link. Unlike print_info(), this one synchronizes with the + driver so the information should be self-consistent (but it will + hang if the driver is wedged). */ +int pvr2_debugifc_print_info(struct pvr2_hdw *, + char *buf_ptr, unsigned int buf_size); + +/* Non-intrusively print some useful debugging info from inside the + driver. This should work even if the driver appears to be + wedged. */ +int pvr2_debugifc_print_status(struct pvr2_hdw *, + char *buf_ptr,unsigned int buf_size); + +/* Parse a string command into a driver action. */ +int pvr2_debugifc_docmd(struct pvr2_hdw *, + const char *buf_ptr,unsigned int buf_size); + +#endif /* __PVRUSB2_DEBUGIFC_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-devattr.c b/drivers/media/usb/pvrusb2/pvrusb2-devattr.c new file mode 100644 index 0000000000..d1b984ec75 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-devattr.c @@ -0,0 +1,718 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2007 Mike Isely <isely@pobox.com> + */ + +/* + +This source file should encompass ALL per-device type information for the +driver. To define a new device, add elements to the pvr2_device_table and +pvr2_device_desc structures. + +*/ + +#include "pvrusb2-devattr.h" +#include <linux/usb.h> +#include <linux/module.h> +/* This is needed in order to pull in tuner type ids... */ +#include <linux/i2c.h> +#include <media/tuner.h> +#ifdef CONFIG_VIDEO_PVRUSB2_DVB +#include "pvrusb2-hdw-internal.h" +#include "lgdt330x.h" +#include "s5h1409.h" +#include "s5h1411.h" +#include "tda10048.h" +#include "tda18271.h" +#include "tda8290.h" +#include "tuner-simple.h" +#include "si2157.h" +#include "lgdt3306a.h" +#include "si2168.h" +#endif + + +/*------------------------------------------------------------------------*/ +/* Hauppauge PVR-USB2 Model 29xxx */ + +static const struct pvr2_device_client_desc pvr2_cli_29xxx[] = { + { .module_id = PVR2_CLIENT_ID_SAA7115 }, + { .module_id = PVR2_CLIENT_ID_MSP3400 }, + { .module_id = PVR2_CLIENT_ID_TUNER }, + { .module_id = PVR2_CLIENT_ID_DEMOD }, +}; + +#define PVR2_FIRMWARE_29xxx "v4l-pvrusb2-29xxx-01.fw" +static const char *pvr2_fw1_names_29xxx[] = { + PVR2_FIRMWARE_29xxx, +}; + +static const struct pvr2_device_desc pvr2_device_29xxx = { + .description = "WinTV PVR USB2 Model 29xxx", + .shortname = "29xxx", + .client_table.lst = pvr2_cli_29xxx, + .client_table.cnt = ARRAY_SIZE(pvr2_cli_29xxx), + .fx2_firmware.lst = pvr2_fw1_names_29xxx, + .fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_29xxx), + .flag_has_hauppauge_rom = !0, + .flag_has_analogtuner = !0, + .flag_has_fmradio = !0, + .flag_has_composite = !0, + .flag_has_svideo = !0, + .signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE, + .led_scheme = PVR2_LED_SCHEME_HAUPPAUGE, + .ir_scheme = PVR2_IR_SCHEME_29XXX, +}; + + + +/*------------------------------------------------------------------------*/ +/* Hauppauge PVR-USB2 Model 24xxx */ + +static const struct pvr2_device_client_desc pvr2_cli_24xxx[] = { + { .module_id = PVR2_CLIENT_ID_CX25840 }, + { .module_id = PVR2_CLIENT_ID_TUNER }, + { .module_id = PVR2_CLIENT_ID_WM8775 }, + { .module_id = PVR2_CLIENT_ID_DEMOD }, +}; + +#define PVR2_FIRMWARE_24xxx "v4l-pvrusb2-24xxx-01.fw" +static const char *pvr2_fw1_names_24xxx[] = { + PVR2_FIRMWARE_24xxx, +}; + +static const struct pvr2_device_desc pvr2_device_24xxx = { + .description = "WinTV PVR USB2 Model 24xxx", + .shortname = "24xxx", + .client_table.lst = pvr2_cli_24xxx, + .client_table.cnt = ARRAY_SIZE(pvr2_cli_24xxx), + .fx2_firmware.lst = pvr2_fw1_names_24xxx, + .fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_24xxx), + .flag_has_cx25840 = !0, + .flag_has_wm8775 = !0, + .flag_has_hauppauge_rom = !0, + .flag_has_analogtuner = !0, + .flag_has_fmradio = !0, + .flag_has_composite = !0, + .flag_has_svideo = !0, + .signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE, + .led_scheme = PVR2_LED_SCHEME_HAUPPAUGE, + .ir_scheme = PVR2_IR_SCHEME_24XXX, +}; + + + +/*------------------------------------------------------------------------*/ +/* GOTVIEW USB2.0 DVD2 */ + +static const struct pvr2_device_client_desc pvr2_cli_gotview_2[] = { + { .module_id = PVR2_CLIENT_ID_CX25840 }, + { .module_id = PVR2_CLIENT_ID_TUNER }, + { .module_id = PVR2_CLIENT_ID_DEMOD }, +}; + +static const struct pvr2_device_desc pvr2_device_gotview_2 = { + .description = "Gotview USB 2.0 DVD 2", + .shortname = "gv2", + .client_table.lst = pvr2_cli_gotview_2, + .client_table.cnt = ARRAY_SIZE(pvr2_cli_gotview_2), + .flag_has_cx25840 = !0, + .default_tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .flag_has_analogtuner = !0, + .flag_has_fmradio = !0, + .flag_has_composite = !0, + .flag_has_svideo = !0, + .signal_routing_scheme = PVR2_ROUTING_SCHEME_GOTVIEW, +}; + + + +/*------------------------------------------------------------------------*/ +/* GOTVIEW USB2.0 DVD Deluxe */ + +/* (same module list as gotview_2) */ + +static const struct pvr2_device_desc pvr2_device_gotview_2d = { + .description = "Gotview USB 2.0 DVD Deluxe", + .shortname = "gv2d", + .client_table.lst = pvr2_cli_gotview_2, + .client_table.cnt = ARRAY_SIZE(pvr2_cli_gotview_2), + .flag_has_cx25840 = !0, + .default_tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .flag_has_analogtuner = !0, + .flag_has_composite = !0, + .flag_has_svideo = !0, + .signal_routing_scheme = PVR2_ROUTING_SCHEME_GOTVIEW, +}; + + + +/*------------------------------------------------------------------------*/ +/* Terratec Grabster AV400 */ + +static const struct pvr2_device_client_desc pvr2_cli_av400[] = { + { .module_id = PVR2_CLIENT_ID_CX25840 }, +}; + +static const struct pvr2_device_desc pvr2_device_av400 = { + .description = "Terratec Grabster AV400", + .shortname = "av400", + .flag_is_experimental = 1, + .client_table.lst = pvr2_cli_av400, + .client_table.cnt = ARRAY_SIZE(pvr2_cli_av400), + .flag_has_cx25840 = !0, + .flag_has_analogtuner = 0, + .flag_has_composite = !0, + .flag_has_svideo = !0, + .signal_routing_scheme = PVR2_ROUTING_SCHEME_AV400, +}; + + + +/*------------------------------------------------------------------------*/ +/* OnAir Creator */ + +#ifdef CONFIG_VIDEO_PVRUSB2_DVB +static struct lgdt330x_config pvr2_lgdt3303_config = { + .demod_chip = LGDT3303, + .clock_polarity_flip = 1, +}; + +static int pvr2_lgdt3303_attach(struct pvr2_dvb_adapter *adap) +{ + adap->fe[0] = dvb_attach(lgdt330x_attach, &pvr2_lgdt3303_config, + 0x0e, + &adap->channel.hdw->i2c_adap); + if (adap->fe[0]) + return 0; + + return -EIO; +} + +static int pvr2_lgh06xf_attach(struct pvr2_dvb_adapter *adap) +{ + dvb_attach(simple_tuner_attach, adap->fe[0], + &adap->channel.hdw->i2c_adap, 0x61, + TUNER_LG_TDVS_H06XF); + + return 0; +} + +static const struct pvr2_dvb_props pvr2_onair_creator_fe_props = { + .frontend_attach = pvr2_lgdt3303_attach, + .tuner_attach = pvr2_lgh06xf_attach, +}; +#endif + +static const struct pvr2_device_client_desc pvr2_cli_onair_creator[] = { + { .module_id = PVR2_CLIENT_ID_SAA7115 }, + { .module_id = PVR2_CLIENT_ID_CS53L32A }, + { .module_id = PVR2_CLIENT_ID_TUNER }, +}; + +static const struct pvr2_device_desc pvr2_device_onair_creator = { + .description = "OnAir Creator Hybrid USB tuner", + .shortname = "oac", + .client_table.lst = pvr2_cli_onair_creator, + .client_table.cnt = ARRAY_SIZE(pvr2_cli_onair_creator), + .default_tuner_type = TUNER_LG_TDVS_H06XF, + .flag_has_analogtuner = !0, + .flag_has_composite = !0, + .flag_has_svideo = !0, + .flag_digital_requires_cx23416 = !0, + .signal_routing_scheme = PVR2_ROUTING_SCHEME_ONAIR, + .digital_control_scheme = PVR2_DIGITAL_SCHEME_ONAIR, + .default_std_mask = V4L2_STD_NTSC_M, +#ifdef CONFIG_VIDEO_PVRUSB2_DVB + .dvb_props = &pvr2_onair_creator_fe_props, +#endif +}; + + + +/*------------------------------------------------------------------------*/ +/* OnAir USB 2.0 */ + +#ifdef CONFIG_VIDEO_PVRUSB2_DVB +static struct lgdt330x_config pvr2_lgdt3302_config = { + .demod_chip = LGDT3302, +}; + +static int pvr2_lgdt3302_attach(struct pvr2_dvb_adapter *adap) +{ + adap->fe[0] = dvb_attach(lgdt330x_attach, &pvr2_lgdt3302_config, + 0x0e, + &adap->channel.hdw->i2c_adap); + if (adap->fe[0]) + return 0; + + return -EIO; +} + +static int pvr2_fcv1236d_attach(struct pvr2_dvb_adapter *adap) +{ + dvb_attach(simple_tuner_attach, adap->fe[0], + &adap->channel.hdw->i2c_adap, 0x61, + TUNER_PHILIPS_FCV1236D); + + return 0; +} + +static const struct pvr2_dvb_props pvr2_onair_usb2_fe_props = { + .frontend_attach = pvr2_lgdt3302_attach, + .tuner_attach = pvr2_fcv1236d_attach, +}; +#endif + +static const struct pvr2_device_client_desc pvr2_cli_onair_usb2[] = { + { .module_id = PVR2_CLIENT_ID_SAA7115 }, + { .module_id = PVR2_CLIENT_ID_CS53L32A }, + { .module_id = PVR2_CLIENT_ID_TUNER }, +}; + +static const struct pvr2_device_desc pvr2_device_onair_usb2 = { + .description = "OnAir USB2 Hybrid USB tuner", + .shortname = "oa2", + .client_table.lst = pvr2_cli_onair_usb2, + .client_table.cnt = ARRAY_SIZE(pvr2_cli_onair_usb2), + .default_tuner_type = TUNER_PHILIPS_FCV1236D, + .flag_has_analogtuner = !0, + .flag_has_composite = !0, + .flag_has_svideo = !0, + .flag_digital_requires_cx23416 = !0, + .signal_routing_scheme = PVR2_ROUTING_SCHEME_ONAIR, + .digital_control_scheme = PVR2_DIGITAL_SCHEME_ONAIR, + .default_std_mask = V4L2_STD_NTSC_M, +#ifdef CONFIG_VIDEO_PVRUSB2_DVB + .dvb_props = &pvr2_onair_usb2_fe_props, +#endif +}; + + + +/*------------------------------------------------------------------------*/ +/* Hauppauge PVR-USB2 Model 73xxx */ + +#ifdef CONFIG_VIDEO_PVRUSB2_DVB +static struct tda10048_config hauppauge_tda10048_config = { + .demod_address = 0x10 >> 1, + .output_mode = TDA10048_PARALLEL_OUTPUT, + .fwbulkwritelen = TDA10048_BULKWRITE_50, + .inversion = TDA10048_INVERSION_ON, + .dtv6_if_freq_khz = TDA10048_IF_3300, + .dtv7_if_freq_khz = TDA10048_IF_3800, + .dtv8_if_freq_khz = TDA10048_IF_4300, + .clk_freq_khz = TDA10048_CLK_16000, + .disable_gate_access = 1, +}; + +static struct tda829x_config tda829x_no_probe = { + .probe_tuner = TDA829X_DONT_PROBE, +}; + +static struct tda18271_std_map hauppauge_tda18271_dvbt_std_map = { + .dvbt_6 = { .if_freq = 3300, .agc_mode = 3, .std = 4, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_7 = { .if_freq = 3800, .agc_mode = 3, .std = 5, + .if_lvl = 1, .rfagc_top = 0x37, }, + .dvbt_8 = { .if_freq = 4300, .agc_mode = 3, .std = 6, + .if_lvl = 1, .rfagc_top = 0x37, }, +}; + +static struct tda18271_config hauppauge_tda18271_dvb_config = { + .std_map = &hauppauge_tda18271_dvbt_std_map, + .gate = TDA18271_GATE_ANALOG, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static int pvr2_tda10048_attach(struct pvr2_dvb_adapter *adap) +{ + adap->fe[0] = dvb_attach(tda10048_attach, &hauppauge_tda10048_config, + &adap->channel.hdw->i2c_adap); + if (adap->fe[0]) + return 0; + + return -EIO; +} + +static int pvr2_73xxx_tda18271_8295_attach(struct pvr2_dvb_adapter *adap) +{ + dvb_attach(tda829x_attach, adap->fe[0], + &adap->channel.hdw->i2c_adap, 0x42, + &tda829x_no_probe); + dvb_attach(tda18271_attach, adap->fe[0], 0x60, + &adap->channel.hdw->i2c_adap, + &hauppauge_tda18271_dvb_config); + + return 0; +} + +static const struct pvr2_dvb_props pvr2_73xxx_dvb_props = { + .frontend_attach = pvr2_tda10048_attach, + .tuner_attach = pvr2_73xxx_tda18271_8295_attach, +}; +#endif + +static const struct pvr2_device_client_desc pvr2_cli_73xxx[] = { + { .module_id = PVR2_CLIENT_ID_CX25840 }, + { .module_id = PVR2_CLIENT_ID_TUNER, + .i2c_address_list = "\x42"}, +}; + +#define PVR2_FIRMWARE_73xxx "v4l-pvrusb2-73xxx-01.fw" +static const char *pvr2_fw1_names_73xxx[] = { + PVR2_FIRMWARE_73xxx, +}; + +static const struct pvr2_device_desc pvr2_device_73xxx = { + .description = "WinTV HVR-1900 Model 73xxx", + .shortname = "73xxx", + .client_table.lst = pvr2_cli_73xxx, + .client_table.cnt = ARRAY_SIZE(pvr2_cli_73xxx), + .fx2_firmware.lst = pvr2_fw1_names_73xxx, + .fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_73xxx), + .flag_has_cx25840 = !0, + .flag_has_hauppauge_rom = !0, + .flag_has_analogtuner = !0, + .flag_has_composite = !0, + .flag_has_svideo = !0, + .flag_fx2_16kb = !0, + .signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE, + .digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE, + .led_scheme = PVR2_LED_SCHEME_HAUPPAUGE, + .ir_scheme = PVR2_IR_SCHEME_ZILOG, +#ifdef CONFIG_VIDEO_PVRUSB2_DVB + .dvb_props = &pvr2_73xxx_dvb_props, +#endif +}; + + + +/*------------------------------------------------------------------------*/ +/* Hauppauge PVR-USB2 Model 75xxx */ + +#ifdef CONFIG_VIDEO_PVRUSB2_DVB +static struct s5h1409_config pvr2_s5h1409_config = { + .demod_address = 0x32 >> 1, + .output_mode = S5H1409_PARALLEL_OUTPUT, + .gpio = S5H1409_GPIO_OFF, + .qam_if = 4000, + .inversion = S5H1409_INVERSION_ON, + .status_mode = S5H1409_DEMODLOCKING, +}; + +static struct s5h1411_config pvr2_s5h1411_config = { + .output_mode = S5H1411_PARALLEL_OUTPUT, + .gpio = S5H1411_GPIO_OFF, + .vsb_if = S5H1411_IF_44000, + .qam_if = S5H1411_IF_4000, + .inversion = S5H1411_INVERSION_ON, + .status_mode = S5H1411_DEMODLOCKING, +}; + +static struct tda18271_std_map hauppauge_tda18271_std_map = { + .atsc_6 = { .if_freq = 5380, .agc_mode = 3, .std = 3, + .if_lvl = 6, .rfagc_top = 0x37, }, + .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 0, + .if_lvl = 6, .rfagc_top = 0x37, }, +}; + +static struct tda18271_config hauppauge_tda18271_config = { + .std_map = &hauppauge_tda18271_std_map, + .gate = TDA18271_GATE_ANALOG, + .output_opt = TDA18271_OUTPUT_LT_OFF, +}; + +static int pvr2_s5h1409_attach(struct pvr2_dvb_adapter *adap) +{ + adap->fe[0] = dvb_attach(s5h1409_attach, &pvr2_s5h1409_config, + &adap->channel.hdw->i2c_adap); + if (adap->fe[0]) + return 0; + + return -EIO; +} + +static int pvr2_s5h1411_attach(struct pvr2_dvb_adapter *adap) +{ + adap->fe[0] = dvb_attach(s5h1411_attach, &pvr2_s5h1411_config, + &adap->channel.hdw->i2c_adap); + if (adap->fe[0]) + return 0; + + return -EIO; +} + +static int pvr2_tda18271_8295_attach(struct pvr2_dvb_adapter *adap) +{ + dvb_attach(tda829x_attach, adap->fe[0], + &adap->channel.hdw->i2c_adap, 0x42, + &tda829x_no_probe); + dvb_attach(tda18271_attach, adap->fe[0], 0x60, + &adap->channel.hdw->i2c_adap, + &hauppauge_tda18271_config); + + return 0; +} + +static const struct pvr2_dvb_props pvr2_750xx_dvb_props = { + .frontend_attach = pvr2_s5h1409_attach, + .tuner_attach = pvr2_tda18271_8295_attach, +}; + +static const struct pvr2_dvb_props pvr2_751xx_dvb_props = { + .frontend_attach = pvr2_s5h1411_attach, + .tuner_attach = pvr2_tda18271_8295_attach, +}; +#endif + +#define PVR2_FIRMWARE_75xxx "v4l-pvrusb2-73xxx-01.fw" +static const char *pvr2_fw1_names_75xxx[] = { + PVR2_FIRMWARE_75xxx, +}; + +static const struct pvr2_device_desc pvr2_device_750xx = { + .description = "WinTV HVR-1950 Model 750xx", + .shortname = "750xx", + .client_table.lst = pvr2_cli_73xxx, + .client_table.cnt = ARRAY_SIZE(pvr2_cli_73xxx), + .fx2_firmware.lst = pvr2_fw1_names_75xxx, + .fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_75xxx), + .flag_has_cx25840 = !0, + .flag_has_hauppauge_rom = !0, + .flag_has_analogtuner = !0, + .flag_has_composite = !0, + .flag_has_svideo = !0, + .flag_fx2_16kb = !0, + .signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE, + .digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE, + .default_std_mask = V4L2_STD_NTSC_M, + .led_scheme = PVR2_LED_SCHEME_HAUPPAUGE, + .ir_scheme = PVR2_IR_SCHEME_ZILOG, +#ifdef CONFIG_VIDEO_PVRUSB2_DVB + .dvb_props = &pvr2_750xx_dvb_props, +#endif +}; + +static const struct pvr2_device_desc pvr2_device_751xx = { + .description = "WinTV HVR-1950 Model 751xx", + .shortname = "751xx", + .client_table.lst = pvr2_cli_73xxx, + .client_table.cnt = ARRAY_SIZE(pvr2_cli_73xxx), + .fx2_firmware.lst = pvr2_fw1_names_75xxx, + .fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_75xxx), + .flag_has_cx25840 = !0, + .flag_has_hauppauge_rom = !0, + .flag_has_analogtuner = !0, + .flag_has_composite = !0, + .flag_has_svideo = !0, + .flag_fx2_16kb = !0, + .signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE, + .digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE, + .default_std_mask = V4L2_STD_NTSC_M, + .led_scheme = PVR2_LED_SCHEME_HAUPPAUGE, + .ir_scheme = PVR2_IR_SCHEME_ZILOG, +#ifdef CONFIG_VIDEO_PVRUSB2_DVB + .dvb_props = &pvr2_751xx_dvb_props, +#endif +}; + +/*------------------------------------------------------------------------*/ +/* Hauppauge PVR-USB2 Model 160000 / 160111 -- HVR-1955 / HVR-1975 */ + +#ifdef CONFIG_VIDEO_PVRUSB2_DVB +static int pvr2_si2157_attach(struct pvr2_dvb_adapter *adap); +static int pvr2_si2168_attach(struct pvr2_dvb_adapter *adap); +static int pvr2_dual_fe_attach(struct pvr2_dvb_adapter *adap); +static int pvr2_lgdt3306a_attach(struct pvr2_dvb_adapter *adap); + +static const struct pvr2_dvb_props pvr2_160000_dvb_props = { + .frontend_attach = pvr2_dual_fe_attach, + .tuner_attach = pvr2_si2157_attach, +}; + +static const struct pvr2_dvb_props pvr2_160111_dvb_props = { + .frontend_attach = pvr2_lgdt3306a_attach, + .tuner_attach = pvr2_si2157_attach, +}; + +static int pvr2_si2157_attach(struct pvr2_dvb_adapter *adap) +{ + struct si2157_config si2157_config = {}; + + si2157_config.inversion = 1; + si2157_config.fe = adap->fe[0]; + + adap->i2c_client_tuner = dvb_module_probe("si2157", "si2177", + &adap->channel.hdw->i2c_adap, + 0x60, &si2157_config); + + if (!adap->i2c_client_tuner) + return -ENODEV; + + return 0; +} + +static int pvr2_si2168_attach(struct pvr2_dvb_adapter *adap) +{ + struct si2168_config si2168_config = {}; + struct i2c_adapter *adapter; + + pr_debug("%s()\n", __func__); + + si2168_config.fe = &adap->fe[1]; + si2168_config.i2c_adapter = &adapter; + si2168_config.ts_mode = SI2168_TS_PARALLEL; /*2, 1-serial, 2-parallel.*/ + si2168_config.ts_clock_gapped = 1; /*0-disabled, 1-enabled.*/ + si2168_config.ts_clock_inv = 0; /*0-not-invert, 1-invert*/ + si2168_config.spectral_inversion = 1; /*0-not-invert, 1-invert*/ + + adap->i2c_client_demod[1] = dvb_module_probe("si2168", NULL, + &adap->channel.hdw->i2c_adap, + 0x64, &si2168_config); + + if (!adap->i2c_client_demod[1]) + return -ENODEV; + + return 0; +} + +static int pvr2_lgdt3306a_attach(struct pvr2_dvb_adapter *adap) +{ + struct lgdt3306a_config lgdt3306a_config; + struct i2c_adapter *adapter; + + pr_debug("%s()\n", __func__); + + lgdt3306a_config.fe = &adap->fe[0]; + lgdt3306a_config.i2c_adapter = &adapter; + lgdt3306a_config.deny_i2c_rptr = 1; + lgdt3306a_config.spectral_inversion = 1; + lgdt3306a_config.qam_if_khz = 4000; + lgdt3306a_config.vsb_if_khz = 3250; + lgdt3306a_config.mpeg_mode = LGDT3306A_MPEG_PARALLEL; + lgdt3306a_config.tpclk_edge = LGDT3306A_TPCLK_FALLING_EDGE; + lgdt3306a_config.tpvalid_polarity = LGDT3306A_TP_VALID_LOW; + lgdt3306a_config.xtalMHz = 25; /* demod clock MHz; 24/25 supported */ + + adap->i2c_client_demod[0] = dvb_module_probe("lgdt3306a", NULL, + &adap->channel.hdw->i2c_adap, + 0x59, &lgdt3306a_config); + + if (!adap->i2c_client_demod[0]) + return -ENODEV; + + return 0; +} + +static int pvr2_dual_fe_attach(struct pvr2_dvb_adapter *adap) +{ + pr_debug("%s()\n", __func__); + + if (pvr2_lgdt3306a_attach(adap) != 0) + return -ENODEV; + + if (pvr2_si2168_attach(adap) != 0) { + dvb_module_release(adap->i2c_client_demod[0]); + return -ENODEV; + } + + return 0; +} +#endif + +#define PVR2_FIRMWARE_160xxx "v4l-pvrusb2-160xxx-01.fw" +static const char *pvr2_fw1_names_160xxx[] = { + PVR2_FIRMWARE_160xxx, +}; + +static const struct pvr2_device_client_desc pvr2_cli_160xxx[] = { + { .module_id = PVR2_CLIENT_ID_CX25840 }, +}; + +static const struct pvr2_device_desc pvr2_device_160000 = { + .description = "WinTV HVR-1975 Model 160000", + .shortname = "160000", + .client_table.lst = pvr2_cli_160xxx, + .client_table.cnt = ARRAY_SIZE(pvr2_cli_160xxx), + .fx2_firmware.lst = pvr2_fw1_names_160xxx, + .fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_160xxx), + .default_tuner_type = TUNER_ABSENT, + .flag_has_cx25840 = 1, + .flag_has_hauppauge_rom = 1, + .flag_has_analogtuner = 1, + .flag_has_composite = 1, + .flag_has_svideo = 1, + .flag_fx2_16kb = 1, + .signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE, + .digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE, + .default_std_mask = V4L2_STD_NTSC_M, + .led_scheme = PVR2_LED_SCHEME_HAUPPAUGE, + .ir_scheme = PVR2_IR_SCHEME_ZILOG, +#ifdef CONFIG_VIDEO_PVRUSB2_DVB + .dvb_props = &pvr2_160000_dvb_props, +#endif +}; + +static const struct pvr2_device_desc pvr2_device_160111 = { + .description = "WinTV HVR-1955 Model 160111", + .shortname = "160111", + .client_table.lst = pvr2_cli_160xxx, + .client_table.cnt = ARRAY_SIZE(pvr2_cli_160xxx), + .fx2_firmware.lst = pvr2_fw1_names_160xxx, + .fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_160xxx), + .default_tuner_type = TUNER_ABSENT, + .flag_has_cx25840 = 1, + .flag_has_hauppauge_rom = 1, + .flag_has_analogtuner = 1, + .flag_has_composite = 1, + .flag_has_svideo = 1, + .flag_fx2_16kb = 1, + .signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE, + .digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE, + .default_std_mask = V4L2_STD_NTSC_M, + .led_scheme = PVR2_LED_SCHEME_HAUPPAUGE, + .ir_scheme = PVR2_IR_SCHEME_ZILOG, +#ifdef CONFIG_VIDEO_PVRUSB2_DVB + .dvb_props = &pvr2_160111_dvb_props, +#endif +}; + +/*------------------------------------------------------------------------*/ + +struct usb_device_id pvr2_device_table[] = { + { USB_DEVICE(0x2040, 0x2900), + .driver_info = (kernel_ulong_t)&pvr2_device_29xxx}, + { USB_DEVICE(0x2040, 0x2950), /* Logically identical to 2900 */ + .driver_info = (kernel_ulong_t)&pvr2_device_29xxx}, + { USB_DEVICE(0x2040, 0x2400), + .driver_info = (kernel_ulong_t)&pvr2_device_24xxx}, + { USB_DEVICE(0x1164, 0x0622), + .driver_info = (kernel_ulong_t)&pvr2_device_gotview_2}, + { USB_DEVICE(0x1164, 0x0602), + .driver_info = (kernel_ulong_t)&pvr2_device_gotview_2d}, + { USB_DEVICE(0x11ba, 0x1003), + .driver_info = (kernel_ulong_t)&pvr2_device_onair_creator}, + { USB_DEVICE(0x11ba, 0x1001), + .driver_info = (kernel_ulong_t)&pvr2_device_onair_usb2}, + { USB_DEVICE(0x2040, 0x7300), + .driver_info = (kernel_ulong_t)&pvr2_device_73xxx}, + { USB_DEVICE(0x2040, 0x7500), + .driver_info = (kernel_ulong_t)&pvr2_device_750xx}, + { USB_DEVICE(0x2040, 0x7501), + .driver_info = (kernel_ulong_t)&pvr2_device_751xx}, + { USB_DEVICE(0x0ccd, 0x0039), + .driver_info = (kernel_ulong_t)&pvr2_device_av400}, + { USB_DEVICE(0x2040, 0x7502), + .driver_info = (kernel_ulong_t)&pvr2_device_160111}, + { USB_DEVICE(0x2040, 0x7510), + .driver_info = (kernel_ulong_t)&pvr2_device_160000}, + { } +}; + +MODULE_DEVICE_TABLE(usb, pvr2_device_table); +MODULE_FIRMWARE(PVR2_FIRMWARE_29xxx); +MODULE_FIRMWARE(PVR2_FIRMWARE_24xxx); +MODULE_FIRMWARE(PVR2_FIRMWARE_73xxx); +MODULE_FIRMWARE(PVR2_FIRMWARE_75xxx); diff --git a/drivers/media/usb/pvrusb2/pvrusb2-devattr.h b/drivers/media/usb/pvrusb2/pvrusb2-devattr.h new file mode 100644 index 0000000000..3c88f05d82 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-devattr.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_DEVATTR_H +#define __PVRUSB2_DEVATTR_H + +#include <linux/mod_devicetable.h> +#include <linux/videodev2.h> +#ifdef CONFIG_VIDEO_PVRUSB2_DVB +#include "pvrusb2-dvb.h" +#endif + +/* + + This header defines structures used to describe attributes of a device. + +*/ + + +#define PVR2_CLIENT_ID_NULL 0 +#define PVR2_CLIENT_ID_MSP3400 1 +#define PVR2_CLIENT_ID_CX25840 2 +#define PVR2_CLIENT_ID_SAA7115 3 +#define PVR2_CLIENT_ID_TUNER 4 +#define PVR2_CLIENT_ID_CS53L32A 5 +#define PVR2_CLIENT_ID_WM8775 6 +#define PVR2_CLIENT_ID_DEMOD 7 + +struct pvr2_device_client_desc { + /* One ovr PVR2_CLIENT_ID_xxxx */ + unsigned char module_id; + + /* Null-terminated array of I2C addresses to try in order + initialize the module. It's safe to make this null terminated + since we're never going to encounter an i2c device with an + address of zero. If this is a null pointer or zero-length, + then no I2C addresses have been specified, in which case we'll + try some compiled in defaults for now. */ + unsigned char *i2c_address_list; +}; + +struct pvr2_device_client_table { + const struct pvr2_device_client_desc *lst; + unsigned char cnt; +}; + + +struct pvr2_string_table { + const char **lst; + unsigned int cnt; +}; + +#define PVR2_ROUTING_SCHEME_HAUPPAUGE 0 +#define PVR2_ROUTING_SCHEME_GOTVIEW 1 +#define PVR2_ROUTING_SCHEME_ONAIR 2 +#define PVR2_ROUTING_SCHEME_AV400 3 +#define PVR2_ROUTING_SCHEME_HAUP160XXX 4 + +#define PVR2_DIGITAL_SCHEME_NONE 0 +#define PVR2_DIGITAL_SCHEME_HAUPPAUGE 1 +#define PVR2_DIGITAL_SCHEME_ONAIR 2 + +#define PVR2_LED_SCHEME_NONE 0 +#define PVR2_LED_SCHEME_HAUPPAUGE 1 + +#define PVR2_IR_SCHEME_NONE 0 +#define PVR2_IR_SCHEME_24XXX 1 /* FX2-controlled IR */ +#define PVR2_IR_SCHEME_ZILOG 2 /* HVR-1950 style (must be taken out of reset) */ +#define PVR2_IR_SCHEME_24XXX_MCE 3 /* 24xxx MCE device */ +#define PVR2_IR_SCHEME_29XXX 4 /* Original 29xxx device */ + +/* This describes a particular hardware type (except for the USB device ID + which must live in a separate structure due to environmental + constraints). See the top of pvrusb2-hdw.c for where this is + instantiated. */ +struct pvr2_device_desc { + /* Single line text description of hardware */ + const char *description; + + /* Single token identifier for hardware */ + const char *shortname; + + /* List of additional client modules we need to load */ + struct pvr2_string_table client_modules; + + /* List of defined client modules we need to load */ + struct pvr2_device_client_table client_table; + + /* List of FX2 firmware file names we should search; if empty then + FX2 firmware check / load is skipped and we assume the device + was initialized from internal ROM. */ + struct pvr2_string_table fx2_firmware; + +#ifdef CONFIG_VIDEO_PVRUSB2_DVB + /* callback functions to handle attachment of digital tuner & demod */ + const struct pvr2_dvb_props *dvb_props; + +#endif + /* Initial standard bits to use for this device, if not zero. + Anything set here is also implied as an available standard. + Note: This is ignored if overridden on the module load line via + the video_std module option. */ + v4l2_std_id default_std_mask; + + /* V4L tuner type ID to use with this device (only used if the + driver could not discover the type any other way). */ + int default_tuner_type; + + /* Signal routing scheme used by device, contains one of + PVR2_ROUTING_SCHEME_XXX. Schemes have to be defined as we + encounter them. This is an arbitrary integer scheme id; its + meaning is contained entirely within the driver and is + interpreted by logic which must send commands to the chip-level + drivers (search for things which touch this field). */ + unsigned char signal_routing_scheme; + + /* Indicates scheme for controlling device's LED (if any). The + driver will turn on the LED when streaming is underway. This + contains one of PVR2_LED_SCHEME_XXX. */ + unsigned char led_scheme; + + /* Control scheme to use if there is a digital tuner. This + contains one of PVR2_DIGITAL_SCHEME_XXX. This is an arbitrary + integer scheme id; its meaning is contained entirely within the + driver and is interpreted by logic which must control the + streaming pathway (search for things which touch this field). */ + unsigned char digital_control_scheme; + + /* If set, we don't bother trying to load cx23416 firmware. */ + unsigned int flag_skip_cx23416_firmware:1; + + /* If set, the encoder must be healthy in order for digital mode to + work (otherwise we assume that digital streaming will work even + if we fail to locate firmware for the encoder). If the device + doesn't support digital streaming then this flag has no + effect. */ + unsigned int flag_digital_requires_cx23416:1; + + /* Device has a hauppauge eeprom which we can interrogate. */ + unsigned int flag_has_hauppauge_rom:1; + + /* Device does not require a powerup command to be issued. */ + unsigned int flag_no_powerup:1; + + /* Device has a cx25840 - this enables special additional logic to + handle it. */ + unsigned int flag_has_cx25840:1; + + /* Device has a wm8775 - this enables special additional logic to + ensure that it is found. */ + unsigned int flag_has_wm8775:1; + + /* Indicate IR scheme of hardware. If not set, then it is assumed + that IR can work without any help from the driver. */ + unsigned int ir_scheme:3; + + /* These bits define which kinds of sources the device can handle. + Note: Digital tuner presence is inferred by the + digital_control_scheme enumeration. */ + unsigned int flag_has_fmradio:1; /* Has FM radio receiver */ + unsigned int flag_has_analogtuner:1; /* Has analog tuner */ + unsigned int flag_has_composite:1; /* Has composite input */ + unsigned int flag_has_svideo:1; /* Has s-video input */ + unsigned int flag_fx2_16kb:1; /* 16KB FX2 firmware OK here */ + + /* If this driver is considered experimental, i.e. not all aspects + are working correctly and/or it is untested, mark that fact + with this flag. */ + unsigned int flag_is_experimental:1; +}; + +extern struct usb_device_id pvr2_device_table[]; + +#endif /* __PVRUSB2_HDW_INTERNAL_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-dvb.c b/drivers/media/usb/pvrusb2/pvrusb2-dvb.c new file mode 100644 index 0000000000..26811efe0f --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-dvb.c @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * pvrusb2-dvb.c - linux-dvb api interface to the pvrusb2 driver. + * + * Copyright (C) 2007, 2008 Michael Krufky <mkrufky@linuxtv.org> + */ + +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <media/dvbdev.h> +#include "pvrusb2-debug.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-io.h" +#include "pvrusb2-dvb.h" + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int pvr2_dvb_feed_func(struct pvr2_dvb_adapter *adap) +{ + int ret; + unsigned int count; + struct pvr2_buffer *bp; + struct pvr2_stream *stream; + + pvr2_trace(PVR2_TRACE_DVB_FEED, "dvb feed thread started"); + set_freezable(); + + stream = adap->channel.stream->stream; + + for (;;) { + if (kthread_should_stop()) break; + + /* Not sure about this... */ + try_to_freeze(); + + bp = pvr2_stream_get_ready_buffer(stream); + if (bp != NULL) { + count = pvr2_buffer_get_count(bp); + if (count) { + dvb_dmx_swfilter( + &adap->demux, + adap->buffer_storage[ + pvr2_buffer_get_id(bp)], + count); + } else { + ret = pvr2_buffer_get_status(bp); + if (ret < 0) break; + } + ret = pvr2_buffer_queue(bp); + if (ret < 0) break; + + /* Since we know we did something to a buffer, + just go back and try again. No point in + blocking unless we really ran out of + buffers to process. */ + continue; + } + + + /* Wait until more buffers become available or we're + told not to wait any longer. */ + ret = wait_event_interruptible( + adap->buffer_wait_data, + (pvr2_stream_get_ready_count(stream) > 0) || + kthread_should_stop()); + if (ret < 0) break; + } + + /* If we get here and ret is < 0, then an error has occurred. + Probably would be a good idea to communicate that to DVB core... */ + + pvr2_trace(PVR2_TRACE_DVB_FEED, "dvb feed thread stopped"); + + return 0; +} + +static int pvr2_dvb_feed_thread(void *data) +{ + int stat = pvr2_dvb_feed_func(data); + + while (!kthread_should_stop()) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } + return stat; +} + +static void pvr2_dvb_notify(struct pvr2_dvb_adapter *adap) +{ + wake_up(&adap->buffer_wait_data); +} + +static void pvr2_dvb_stream_end(struct pvr2_dvb_adapter *adap) +{ + unsigned int idx; + struct pvr2_stream *stream; + + if (adap->thread) { + kthread_stop(adap->thread); + adap->thread = NULL; + } + + if (adap->channel.stream) { + stream = adap->channel.stream->stream; + } else { + stream = NULL; + } + if (stream) { + pvr2_hdw_set_streaming(adap->channel.hdw, 0); + pvr2_stream_set_callback(stream, NULL, NULL); + pvr2_stream_kill(stream); + pvr2_stream_set_buffer_count(stream, 0); + pvr2_channel_claim_stream(&adap->channel, NULL); + } + + if (adap->stream_run) { + for (idx = 0; idx < PVR2_DVB_BUFFER_COUNT; idx++) { + if (!(adap->buffer_storage[idx])) continue; + kfree(adap->buffer_storage[idx]); + adap->buffer_storage[idx] = NULL; + } + adap->stream_run = 0; + } +} + +static int pvr2_dvb_stream_do_start(struct pvr2_dvb_adapter *adap) +{ + struct pvr2_context *pvr = adap->channel.mc_head; + unsigned int idx; + int ret; + struct pvr2_buffer *bp; + struct pvr2_stream *stream = NULL; + + if (adap->stream_run) return -EIO; + + ret = pvr2_channel_claim_stream(&adap->channel, &pvr->video_stream); + /* somebody else already has the stream */ + if (ret < 0) return ret; + + stream = adap->channel.stream->stream; + + for (idx = 0; idx < PVR2_DVB_BUFFER_COUNT; idx++) { + adap->buffer_storage[idx] = kmalloc(PVR2_DVB_BUFFER_SIZE, + GFP_KERNEL); + if (!(adap->buffer_storage[idx])) return -ENOMEM; + } + + pvr2_stream_set_callback(pvr->video_stream.stream, + (pvr2_stream_callback) pvr2_dvb_notify, adap); + + ret = pvr2_stream_set_buffer_count(stream, PVR2_DVB_BUFFER_COUNT); + if (ret < 0) return ret; + + for (idx = 0; idx < PVR2_DVB_BUFFER_COUNT; idx++) { + bp = pvr2_stream_get_buffer(stream, idx); + pvr2_buffer_set_buffer(bp, + adap->buffer_storage[idx], + PVR2_DVB_BUFFER_SIZE); + } + + ret = pvr2_hdw_set_streaming(adap->channel.hdw, 1); + if (ret < 0) return ret; + + while ((bp = pvr2_stream_get_idle_buffer(stream)) != NULL) { + ret = pvr2_buffer_queue(bp); + if (ret < 0) return ret; + } + + adap->thread = kthread_run(pvr2_dvb_feed_thread, adap, "pvrusb2-dvb"); + + if (IS_ERR(adap->thread)) { + ret = PTR_ERR(adap->thread); + adap->thread = NULL; + return ret; + } + + adap->stream_run = !0; + + return 0; +} + +static int pvr2_dvb_stream_start(struct pvr2_dvb_adapter *adap) +{ + int ret = pvr2_dvb_stream_do_start(adap); + if (ret < 0) pvr2_dvb_stream_end(adap); + return ret; +} + +static int pvr2_dvb_ctrl_feed(struct dvb_demux_feed *dvbdmxfeed, int onoff) +{ + struct pvr2_dvb_adapter *adap = dvbdmxfeed->demux->priv; + int ret = 0; + + if (adap == NULL) return -ENODEV; + + mutex_lock(&adap->lock); + do { + if (onoff) { + if (!adap->feedcount) { + pvr2_trace(PVR2_TRACE_DVB_FEED, + "start feeding demux"); + ret = pvr2_dvb_stream_start(adap); + if (ret < 0) break; + } + (adap->feedcount)++; + } else if (adap->feedcount > 0) { + (adap->feedcount)--; + if (!adap->feedcount) { + pvr2_trace(PVR2_TRACE_DVB_FEED, + "stop feeding demux"); + pvr2_dvb_stream_end(adap); + } + } + } while (0); + mutex_unlock(&adap->lock); + + return ret; +} + +static int pvr2_dvb_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + pvr2_trace(PVR2_TRACE_DVB_FEED, "start pid: 0x%04x", dvbdmxfeed->pid); + return pvr2_dvb_ctrl_feed(dvbdmxfeed, 1); +} + +static int pvr2_dvb_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + pvr2_trace(PVR2_TRACE_DVB_FEED, "stop pid: 0x%04x", dvbdmxfeed->pid); + return pvr2_dvb_ctrl_feed(dvbdmxfeed, 0); +} + +static int pvr2_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire) +{ + struct pvr2_dvb_adapter *adap = fe->dvb->priv; + return pvr2_channel_limit_inputs( + &adap->channel, + (acquire ? (1 << PVR2_CVAL_INPUT_DTV) : 0)); +} + +static int pvr2_dvb_adapter_init(struct pvr2_dvb_adapter *adap) +{ + int ret; + + ret = dvb_register_adapter(&adap->dvb_adap, "pvrusb2-dvb", + THIS_MODULE/*&hdw->usb_dev->owner*/, + &adap->channel.hdw->usb_dev->dev, + adapter_nr); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "dvb_register_adapter failed: error %d", ret); + goto err; + } + adap->dvb_adap.priv = adap; + + adap->demux.dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING; + adap->demux.priv = adap; + adap->demux.filternum = 256; + adap->demux.feednum = 256; + adap->demux.start_feed = pvr2_dvb_start_feed; + adap->demux.stop_feed = pvr2_dvb_stop_feed; + adap->demux.write_to_decoder = NULL; + + ret = dvb_dmx_init(&adap->demux); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "dvb_dmx_init failed: error %d", ret); + goto err_dmx; + } + + adap->dmxdev.filternum = adap->demux.filternum; + adap->dmxdev.demux = &adap->demux.dmx; + adap->dmxdev.capabilities = 0; + + ret = dvb_dmxdev_init(&adap->dmxdev, &adap->dvb_adap); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "dvb_dmxdev_init failed: error %d", ret); + goto err_dmx_dev; + } + + dvb_net_init(&adap->dvb_adap, &adap->dvb_net, &adap->demux.dmx); + + return 0; + +err_dmx_dev: + dvb_dmx_release(&adap->demux); +err_dmx: + dvb_unregister_adapter(&adap->dvb_adap); +err: + return ret; +} + +static int pvr2_dvb_adapter_exit(struct pvr2_dvb_adapter *adap) +{ + pvr2_trace(PVR2_TRACE_INFO, "unregistering DVB devices"); + dvb_net_release(&adap->dvb_net); + adap->demux.dmx.close(&adap->demux.dmx); + dvb_dmxdev_release(&adap->dmxdev); + dvb_dmx_release(&adap->demux); + dvb_unregister_adapter(&adap->dvb_adap); + return 0; +} + +static int pvr2_dvb_frontend_init(struct pvr2_dvb_adapter *adap) +{ + struct pvr2_hdw *hdw = adap->channel.hdw; + const struct pvr2_dvb_props *dvb_props = hdw->hdw_desc->dvb_props; + int ret = 0; + + if (dvb_props == NULL) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, "fe_props not defined!"); + return -EINVAL; + } + + ret = pvr2_channel_limit_inputs( + &adap->channel, + (1 << PVR2_CVAL_INPUT_DTV)); + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "failed to grab control of dtv input (code=%d)", + ret); + return ret; + } + + if (dvb_props->frontend_attach == NULL) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "frontend_attach not defined!"); + ret = -EINVAL; + goto done; + } + + if (dvb_props->frontend_attach(adap) == 0 && adap->fe[0]) { + if (dvb_register_frontend(&adap->dvb_adap, adap->fe[0])) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "frontend registration failed!"); + ret = -ENODEV; + goto fail_frontend0; + } + if (adap->fe[0]->ops.analog_ops.standby) + adap->fe[0]->ops.analog_ops.standby(adap->fe[0]); + + pvr2_trace(PVR2_TRACE_INFO, "transferring fe[%d] ts_bus_ctrl() to pvr2_dvb_bus_ctrl()", + adap->fe[0]->id); + adap->fe[0]->ops.ts_bus_ctrl = pvr2_dvb_bus_ctrl; + } else { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "no frontend was attached!"); + ret = -ENODEV; + return ret; + } + + if (dvb_props->tuner_attach && dvb_props->tuner_attach(adap)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, "tuner attach failed"); + ret = -ENODEV; + goto fail_tuner; + } + + if (adap->fe[1]) { + adap->fe[1]->id = 1; + adap->fe[1]->tuner_priv = adap->fe[0]->tuner_priv; + memcpy(&adap->fe[1]->ops.tuner_ops, + &adap->fe[0]->ops.tuner_ops, + sizeof(struct dvb_tuner_ops)); + + if (dvb_register_frontend(&adap->dvb_adap, adap->fe[1])) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "frontend registration failed!"); + ret = -ENODEV; + goto fail_frontend1; + } + /* MFE lock */ + adap->dvb_adap.mfe_shared = 1; + + if (adap->fe[1]->ops.analog_ops.standby) + adap->fe[1]->ops.analog_ops.standby(adap->fe[1]); + + pvr2_trace(PVR2_TRACE_INFO, "transferring fe[%d] ts_bus_ctrl() to pvr2_dvb_bus_ctrl()", + adap->fe[1]->id); + adap->fe[1]->ops.ts_bus_ctrl = pvr2_dvb_bus_ctrl; + } +done: + pvr2_channel_limit_inputs(&adap->channel, 0); + return ret; + +fail_frontend1: + dvb_frontend_detach(adap->fe[1]); + adap->fe[1] = NULL; +fail_tuner: + dvb_unregister_frontend(adap->fe[0]); +fail_frontend0: + dvb_frontend_detach(adap->fe[0]); + adap->fe[0] = NULL; + dvb_module_release(adap->i2c_client_tuner); + dvb_module_release(adap->i2c_client_demod[1]); + dvb_module_release(adap->i2c_client_demod[0]); + + return ret; +} + +static int pvr2_dvb_frontend_exit(struct pvr2_dvb_adapter *adap) +{ + if (adap->fe[1]) { + dvb_unregister_frontend(adap->fe[1]); + dvb_frontend_detach(adap->fe[1]); + adap->fe[1] = NULL; + } + if (adap->fe[0]) { + dvb_unregister_frontend(adap->fe[0]); + dvb_frontend_detach(adap->fe[0]); + adap->fe[0] = NULL; + } + + dvb_module_release(adap->i2c_client_tuner); + adap->i2c_client_tuner = NULL; + dvb_module_release(adap->i2c_client_demod[1]); + adap->i2c_client_demod[1] = NULL; + dvb_module_release(adap->i2c_client_demod[0]); + adap->i2c_client_demod[0] = NULL; + + return 0; +} + +static void pvr2_dvb_destroy(struct pvr2_dvb_adapter *adap) +{ + pvr2_dvb_stream_end(adap); + pvr2_dvb_frontend_exit(adap); + pvr2_dvb_adapter_exit(adap); + pvr2_channel_done(&adap->channel); + kfree(adap); +} + +static void pvr2_dvb_internal_check(struct pvr2_channel *chp) +{ + struct pvr2_dvb_adapter *adap; + adap = container_of(chp, struct pvr2_dvb_adapter, channel); + if (!adap->channel.mc_head->disconnect_flag) return; + pvr2_dvb_destroy(adap); +} + +struct pvr2_dvb_adapter *pvr2_dvb_create(struct pvr2_context *pvr) +{ + int ret = 0; + struct pvr2_dvb_adapter *adap; + if (!pvr->hdw->hdw_desc->dvb_props) { + /* Device lacks a digital interface so don't set up + the DVB side of the driver either. For now. */ + return NULL; + } + adap = kzalloc(sizeof(*adap), GFP_KERNEL); + if (!adap) return adap; + pvr2_channel_init(&adap->channel, pvr); + adap->channel.check_func = pvr2_dvb_internal_check; + init_waitqueue_head(&adap->buffer_wait_data); + mutex_init(&adap->lock); + ret = pvr2_dvb_adapter_init(adap); + if (ret < 0) goto fail1; + ret = pvr2_dvb_frontend_init(adap); + if (ret < 0) goto fail2; + return adap; + +fail2: + pvr2_dvb_adapter_exit(adap); +fail1: + pvr2_channel_done(&adap->channel); + return NULL; +} + diff --git a/drivers/media/usb/pvrusb2/pvrusb2-dvb.h b/drivers/media/usb/pvrusb2/pvrusb2-dvb.h new file mode 100644 index 0000000000..c0b27f5211 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-dvb.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __PVRUSB2_DVB_H__ +#define __PVRUSB2_DVB_H__ + +#include <media/dvb_frontend.h> +#include <media/dvb_demux.h> +#include <media/dvb_net.h> +#include <media/dmxdev.h> +#include "pvrusb2-context.h" + +#define PVR2_DVB_BUFFER_COUNT 32 +#define PVR2_DVB_BUFFER_SIZE PAGE_ALIGN(0x4000) + +struct pvr2_dvb_adapter { + struct pvr2_channel channel; + + struct dvb_adapter dvb_adap; + struct dmxdev dmxdev; + struct dvb_demux demux; + struct dvb_net dvb_net; + struct dvb_frontend *fe[2]; + + struct i2c_client *i2c_client_demod[2]; + struct i2c_client *i2c_client_tuner; + + int feedcount; + int max_feed_count; + + struct task_struct *thread; + struct mutex lock; + + unsigned int stream_run:1; + + wait_queue_head_t buffer_wait_data; + char *buffer_storage[PVR2_DVB_BUFFER_COUNT]; +}; + +struct pvr2_dvb_props { + int (*frontend_attach) (struct pvr2_dvb_adapter *); + int (*tuner_attach) (struct pvr2_dvb_adapter *); +}; + +struct pvr2_dvb_adapter *pvr2_dvb_create(struct pvr2_context *pvr); + +#endif /* __PVRUSB2_DVB_H__ */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-eeprom.c b/drivers/media/usb/pvrusb2/pvrusb2-eeprom.c new file mode 100644 index 0000000000..8e81af5379 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-eeprom.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#include <linux/slab.h> +#include "pvrusb2-eeprom.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" + +#define trace_eeprom(...) pvr2_trace(PVR2_TRACE_EEPROM,__VA_ARGS__) + + + +/* + + Read and analyze data in the eeprom. Use tveeprom to figure out + the packet structure, since this is another Hauppauge device and + internally it has a family resemblance to ivtv-type devices + +*/ + +#include <media/tveeprom.h> + +/* We seem to only be interested in the last 128 bytes of the EEPROM */ +#define EEPROM_SIZE 128 + +/* Grab EEPROM contents, needed for direct method. */ +static u8 *pvr2_eeprom_fetch(struct pvr2_hdw *hdw) +{ + struct i2c_msg msg[2]; + u8 *eeprom; + u8 iadd[2]; + u8 addr; + u16 eepromSize; + unsigned int offs; + int ret; + int mode16 = 0; + unsigned pcnt,tcnt; + eeprom = kzalloc(EEPROM_SIZE, GFP_KERNEL); + if (!eeprom) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to allocate memory required to read eeprom"); + return NULL; + } + + trace_eeprom("Value for eeprom addr from controller was 0x%x", + hdw->eeprom_addr); + addr = hdw->eeprom_addr; + /* Seems that if the high bit is set, then the *real* eeprom + address is shifted right now bit position (noticed this in + newer PVR USB2 hardware) */ + if (addr & 0x80) addr >>= 1; + + /* FX2 documentation states that a 16bit-addressed eeprom is + expected if the I2C address is an odd number (yeah, this is + strange but it's what they do) */ + mode16 = (addr & 1); + eepromSize = (mode16 ? 4096 : 256); + trace_eeprom("Examining %d byte eeprom at location 0x%x using %d bit addressing", + eepromSize, addr, + mode16 ? 16 : 8); + + msg[0].addr = addr; + msg[0].flags = 0; + msg[0].len = mode16 ? 2 : 1; + msg[0].buf = iadd; + msg[1].addr = addr; + msg[1].flags = I2C_M_RD; + + /* We have to do the actual eeprom data fetch ourselves, because + (1) we're only fetching part of the eeprom, and (2) if we were + getting the whole thing our I2C driver can't grab it in one + pass - which is what tveeprom is otherwise going to attempt */ + for (tcnt = 0; tcnt < EEPROM_SIZE; tcnt += pcnt) { + pcnt = 16; + if (pcnt + tcnt > EEPROM_SIZE) pcnt = EEPROM_SIZE-tcnt; + offs = tcnt + (eepromSize - EEPROM_SIZE); + if (mode16) { + iadd[0] = offs >> 8; + iadd[1] = offs; + } else { + iadd[0] = offs; + } + msg[1].len = pcnt; + msg[1].buf = eeprom+tcnt; + if ((ret = i2c_transfer(&hdw->i2c_adap, + msg,ARRAY_SIZE(msg))) != 2) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "eeprom fetch set offs err=%d",ret); + kfree(eeprom); + return NULL; + } + } + return eeprom; +} + + +/* Directly call eeprom analysis function within tveeprom. */ +int pvr2_eeprom_analyze(struct pvr2_hdw *hdw) +{ + u8 *eeprom; + struct tveeprom tvdata; + + memset(&tvdata,0,sizeof(tvdata)); + + eeprom = pvr2_eeprom_fetch(hdw); + if (!eeprom) + return -EINVAL; + + tveeprom_hauppauge_analog(&tvdata, eeprom); + + trace_eeprom("eeprom assumed v4l tveeprom module"); + trace_eeprom("eeprom direct call results:"); + trace_eeprom("has_radio=%d",tvdata.has_radio); + trace_eeprom("tuner_type=%d",tvdata.tuner_type); + trace_eeprom("tuner_formats=0x%x",tvdata.tuner_formats); + trace_eeprom("audio_processor=%d",tvdata.audio_processor); + trace_eeprom("model=%d",tvdata.model); + trace_eeprom("revision=%d",tvdata.revision); + trace_eeprom("serial_number=%d",tvdata.serial_number); + trace_eeprom("rev_str=%s",tvdata.rev_str); + hdw->tuner_type = tvdata.tuner_type; + hdw->tuner_updated = !0; + hdw->serial_number = tvdata.serial_number; + hdw->std_mask_eeprom = tvdata.tuner_formats; + + kfree(eeprom); + + return 0; +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-eeprom.h b/drivers/media/usb/pvrusb2/pvrusb2-eeprom.h new file mode 100644 index 0000000000..a65cc695fd --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-eeprom.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#ifndef __PVRUSB2_EEPROM_H +#define __PVRUSB2_EEPROM_H + +struct pvr2_hdw; + +int pvr2_eeprom_analyze(struct pvr2_hdw *); + +#endif /* __PVRUSB2_EEPROM_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-encoder.c b/drivers/media/usb/pvrusb2/pvrusb2-encoder.c new file mode 100644 index 0000000000..c810277234 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-encoder.c @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#include <linux/device.h> // for linux/firmware.h +#include <linux/firmware.h> +#include "pvrusb2-util.h" +#include "pvrusb2-encoder.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-fx2-cmd.h" + + + +/* Firmware mailbox flags - definitions found from ivtv */ +#define IVTV_MBOX_FIRMWARE_DONE 0x00000004 +#define IVTV_MBOX_DRIVER_DONE 0x00000002 +#define IVTV_MBOX_DRIVER_BUSY 0x00000001 + +#define MBOX_BASE 0x44 + + +static int pvr2_encoder_write_words(struct pvr2_hdw *hdw, + unsigned int offs, + const u32 *data, unsigned int dlen) +{ + unsigned int idx,addr; + unsigned int bAddr; + int ret; + unsigned int chunkCnt; + + /* + + Format: First byte must be 0x01. Remaining 32 bit words are + spread out into chunks of 7 bytes each, with the first 4 bytes + being the data word (little endian), and the next 3 bytes + being the address where that data word is to be written (big + endian). Repeat request for additional words, with offset + adjusted accordingly. + + */ + while (dlen) { + chunkCnt = 8; + if (chunkCnt > dlen) chunkCnt = dlen; + memset(hdw->cmd_buffer,0,sizeof(hdw->cmd_buffer)); + bAddr = 0; + hdw->cmd_buffer[bAddr++] = FX2CMD_MEM_WRITE_DWORD; + for (idx = 0; idx < chunkCnt; idx++) { + addr = idx + offs; + hdw->cmd_buffer[bAddr+6] = (addr & 0xffu); + hdw->cmd_buffer[bAddr+5] = ((addr>>8) & 0xffu); + hdw->cmd_buffer[bAddr+4] = ((addr>>16) & 0xffu); + PVR2_DECOMPOSE_LE(hdw->cmd_buffer, bAddr,data[idx]); + bAddr += 7; + } + ret = pvr2_send_request(hdw, + hdw->cmd_buffer,1+(chunkCnt*7), + NULL,0); + if (ret) return ret; + data += chunkCnt; + dlen -= chunkCnt; + offs += chunkCnt; + } + + return 0; +} + + +static int pvr2_encoder_read_words(struct pvr2_hdw *hdw, + unsigned int offs, + u32 *data, unsigned int dlen) +{ + unsigned int idx; + int ret; + unsigned int chunkCnt; + + /* + + Format: First byte must be 0x02 (status check) or 0x28 (read + back block of 32 bit words). Next 6 bytes must be zero, + followed by a single byte of MBOX_BASE+offset for portion to + be read. Returned data is packed set of 32 bits words that + were read. + + */ + + while (dlen) { + chunkCnt = 16; + if (chunkCnt > dlen) chunkCnt = dlen; + if (chunkCnt < 16) chunkCnt = 1; + hdw->cmd_buffer[0] = + ((chunkCnt == 1) ? + FX2CMD_MEM_READ_DWORD : FX2CMD_MEM_READ_64BYTES); + hdw->cmd_buffer[1] = 0; + hdw->cmd_buffer[2] = 0; + hdw->cmd_buffer[3] = 0; + hdw->cmd_buffer[4] = 0; + hdw->cmd_buffer[5] = ((offs>>16) & 0xffu); + hdw->cmd_buffer[6] = ((offs>>8) & 0xffu); + hdw->cmd_buffer[7] = (offs & 0xffu); + ret = pvr2_send_request(hdw, + hdw->cmd_buffer,8, + hdw->cmd_buffer, + (chunkCnt == 1 ? 4 : 16 * 4)); + if (ret) return ret; + + for (idx = 0; idx < chunkCnt; idx++) { + data[idx] = PVR2_COMPOSE_LE(hdw->cmd_buffer,idx*4); + } + data += chunkCnt; + dlen -= chunkCnt; + offs += chunkCnt; + } + + return 0; +} + + +/* This prototype is set up to be compatible with the + cx2341x_mbox_func prototype in cx2341x.h, which should be in + kernels 2.6.18 or later. We do this so that we can enable + cx2341x.ko to write to our encoder (by handing it a pointer to this + function). For earlier kernels this doesn't really matter. */ +static int pvr2_encoder_cmd(void *ctxt, + u32 cmd, + int arg_cnt_send, + int arg_cnt_recv, + u32 *argp) +{ + unsigned int poll_count; + unsigned int try_count = 0; + int retry_flag; + int ret = 0; + unsigned int idx; + /* These sizes look to be limited by the FX2 firmware implementation */ + u32 wrData[16]; + u32 rdData[16]; + struct pvr2_hdw *hdw = (struct pvr2_hdw *)ctxt; + + + /* + + The encoder seems to speak entirely using blocks 32 bit words. + In ivtv driver terms, this is a mailbox at MBOX_BASE which we + populate with data and watch what the hardware does with it. + The first word is a set of flags used to control the + transaction, the second word is the command to execute, the + third byte is zero (ivtv driver suggests that this is some + kind of return value), and the fourth byte is a specified + timeout (windows driver always uses 0x00060000 except for one + case when it is zero). All successive words are the argument + words for the command. + + First, write out the entire set of words, with the first word + being zero. + + Next, write out just the first word again, but set it to + IVTV_MBOX_DRIVER_DONE | IVTV_DRIVER_BUSY this time (which + probably means "go"). + + Next, read back the return count words. Check the first word, + which should have IVTV_MBOX_FIRMWARE_DONE set. If however + that bit is not set, then the command isn't done so repeat the + read until it is set. + + Finally, write out just the first word again, but set it to + 0x0 this time (which probably means "idle"). + + */ + + if (arg_cnt_send > (ARRAY_SIZE(wrData) - 4)) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Failed to write cx23416 command - too many input arguments (was given %u limit %lu)", + arg_cnt_send, (long unsigned) ARRAY_SIZE(wrData) - 4); + return -EINVAL; + } + + if (arg_cnt_recv > (ARRAY_SIZE(rdData) - 4)) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Failed to write cx23416 command - too many return arguments (was given %u limit %lu)", + arg_cnt_recv, (long unsigned) ARRAY_SIZE(rdData) - 4); + return -EINVAL; + } + + + LOCK_TAKE(hdw->ctl_lock); + while (1) { + if (!hdw->state_encoder_ok) { + ret = -EIO; + break; + } + + retry_flag = 0; + try_count++; + ret = 0; + wrData[0] = 0; + wrData[1] = cmd; + wrData[2] = 0; + wrData[3] = 0x00060000; + for (idx = 0; idx < arg_cnt_send; idx++) { + wrData[idx+4] = argp[idx]; + } + for (; idx < ARRAY_SIZE(wrData) - 4; idx++) { + wrData[idx+4] = 0; + } + + ret = pvr2_encoder_write_words(hdw,MBOX_BASE,wrData,idx); + if (ret) break; + wrData[0] = IVTV_MBOX_DRIVER_DONE|IVTV_MBOX_DRIVER_BUSY; + ret = pvr2_encoder_write_words(hdw,MBOX_BASE,wrData,1); + if (ret) break; + poll_count = 0; + while (1) { + poll_count++; + ret = pvr2_encoder_read_words(hdw,MBOX_BASE,rdData, + arg_cnt_recv+4); + if (ret) { + break; + } + if (rdData[0] & IVTV_MBOX_FIRMWARE_DONE) { + break; + } + if (rdData[0] && (poll_count < 1000)) continue; + if (!rdData[0]) { + retry_flag = !0; + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Encoder timed out waiting for us; arranging to retry"); + } else { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "***WARNING*** device's encoder appears to be stuck (status=0x%08x)", +rdData[0]); + } + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Encoder command: 0x%02x",cmd); + for (idx = 4; idx < arg_cnt_send; idx++) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Encoder arg%d: 0x%08x", + idx-3,wrData[idx]); + } + ret = -EBUSY; + break; + } + if (retry_flag) { + if (try_count < 20) continue; + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Too many retries..."); + ret = -EBUSY; + } + if (ret) { + del_timer_sync(&hdw->encoder_run_timer); + hdw->state_encoder_ok = 0; + pvr2_trace(PVR2_TRACE_STBITS, + "State bit %s <-- %s", + "state_encoder_ok", + (hdw->state_encoder_ok ? "true" : "false")); + if (hdw->state_encoder_runok) { + hdw->state_encoder_runok = 0; + pvr2_trace(PVR2_TRACE_STBITS, + "State bit %s <-- %s", + "state_encoder_runok", + (hdw->state_encoder_runok ? + "true" : "false")); + } + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Giving up on command. This is normally recovered via a firmware reload and re-initialization; concern is only warranted if this happens repeatedly and rapidly."); + break; + } + wrData[0] = 0x7; + for (idx = 0; idx < arg_cnt_recv; idx++) { + argp[idx] = rdData[idx+4]; + } + + wrData[0] = 0x0; + ret = pvr2_encoder_write_words(hdw,MBOX_BASE,wrData,1); + break; + } + LOCK_GIVE(hdw->ctl_lock); + + return ret; +} + + +static int pvr2_encoder_vcmd(struct pvr2_hdw *hdw, int cmd, + int args, ...) +{ + va_list vl; + unsigned int idx; + u32 data[12]; + + if (args > ARRAY_SIZE(data)) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Failed to write cx23416 command - too many arguments (was given %u limit %lu)", + args, (long unsigned) ARRAY_SIZE(data)); + return -EINVAL; + } + + va_start(vl, args); + for (idx = 0; idx < args; idx++) { + data[idx] = va_arg(vl, u32); + } + va_end(vl); + + return pvr2_encoder_cmd(hdw,cmd,args,0,data); +} + + +/* This implements some extra setup for the encoder that seems to be + specific to the PVR USB2 hardware. */ +static int pvr2_encoder_prep_config(struct pvr2_hdw *hdw) +{ + int ret = 0; + int encMisc3Arg = 0; + +#if 0 + /* This inexplicable bit happens in the Hauppauge windows + driver (for both 24xxx and 29xxx devices). However I + currently see no difference in behavior with or without + this stuff. Leave this here as a note of its existence, + but don't use it. */ + LOCK_TAKE(hdw->ctl_lock); do { + u32 dat[1]; + dat[0] = 0x80000640; + pvr2_encoder_write_words(hdw,0x01fe,dat,1); + pvr2_encoder_write_words(hdw,0x023e,dat,1); + } while(0); LOCK_GIVE(hdw->ctl_lock); +#endif + + /* Mike Isely <isely@pobox.com> 26-Jan-2006 The windows driver + sends the following list of ENC_MISC commands (for both + 24xxx and 29xxx devices). Meanings are not entirely clear, + however without the ENC_MISC(3,1) command then we risk + random perpetual video corruption whenever the video input + breaks up for a moment (like when switching channels). */ + + +#if 0 + /* This ENC_MISC(5,0) command seems to hurt 29xxx sync + performance on channel changes, but is not a problem on + 24xxx devices. */ + ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 5,0,0,0); +#endif + + /* This ENC_MISC(3,encMisc3Arg) command is critical - without + it there will eventually be video corruption. Also, the + saa7115 case is strange - the Windows driver is passing 1 + regardless of device type but if we have 1 for saa7115 + devices the video turns sluggish. */ + if (hdw->hdw_desc->flag_has_cx25840) { + encMisc3Arg = 1; + } else { + encMisc3Arg = 0; + } + ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 3, + encMisc3Arg,0,0); + + ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 8,0,0,0); + +#if 0 + /* This ENC_MISC(4,1) command is poisonous, so it is commented + out. But I'm leaving it here anyway to document its + existence in the Windows driver. The effect of this + command is that apps displaying the stream become sluggish + with stuttering video. */ + ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 4,1,0,0); +#endif + + ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 0,3,0,0); + ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4,15,0,0,0); + + /* prevent the PTSs from slowly drifting away in the generated + MPEG stream */ + ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC, 2, 4, 1); + + return ret; +} + +int pvr2_encoder_adjust(struct pvr2_hdw *hdw) +{ + int ret; + ret = cx2341x_update(hdw,pvr2_encoder_cmd, + (hdw->enc_cur_valid ? &hdw->enc_cur_state : NULL), + &hdw->enc_ctl_state); + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Error from cx2341x module code=%d",ret); + } else { + hdw->enc_cur_state = hdw->enc_ctl_state; + hdw->enc_cur_valid = !0; + } + return ret; +} + + +int pvr2_encoder_configure(struct pvr2_hdw *hdw) +{ + int ret; + int val; + pvr2_trace(PVR2_TRACE_ENCODER, "pvr2_encoder_configure (cx2341x module)"); + hdw->enc_ctl_state.port = CX2341X_PORT_STREAMING; + hdw->enc_ctl_state.width = hdw->res_hor_val; + hdw->enc_ctl_state.height = hdw->res_ver_val; + hdw->enc_ctl_state.is_50hz = ((hdw->std_mask_cur & V4L2_STD_525_60) ? + 0 : 1); + + ret = 0; + + ret |= pvr2_encoder_prep_config(hdw); + + /* saa7115: 0xf0 */ + val = 0xf0; + if (hdw->hdw_desc->flag_has_cx25840) { + /* ivtv cx25840: 0x140 */ + val = 0x140; + } + + if (!ret) ret = pvr2_encoder_vcmd( + hdw,CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, + val, val); + + /* setup firmware to notify us about some events (don't know why...) */ + if (!ret) ret = pvr2_encoder_vcmd( + hdw,CX2341X_ENC_SET_EVENT_NOTIFICATION, 4, + 0, 0, 0x10000000, 0xffffffff); + + if (!ret) ret = pvr2_encoder_vcmd( + hdw,CX2341X_ENC_SET_VBI_LINE, 5, + 0xffffffff,0,0,0,0); + + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to configure cx23416"); + return ret; + } + + ret = pvr2_encoder_adjust(hdw); + if (ret) return ret; + + ret = pvr2_encoder_vcmd( + hdw, CX2341X_ENC_INITIALIZE_INPUT, 0); + + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to initialize cx23416 video input"); + return ret; + } + + return 0; +} + + +int pvr2_encoder_start(struct pvr2_hdw *hdw) +{ + int status; + + /* unmask some interrupts */ + pvr2_write_register(hdw, 0x0048, 0xbfffffff); + + pvr2_encoder_vcmd(hdw,CX2341X_ENC_MUTE_VIDEO,1, + hdw->input_val == PVR2_CVAL_INPUT_RADIO ? 1 : 0); + + switch (hdw->active_stream_type) { + case pvr2_config_vbi: + status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2, + 0x01,0x14); + break; + case pvr2_config_mpeg: + status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2, + 0,0x13); + break; + default: /* Unhandled cases for now */ + status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2, + 0,0x13); + break; + } + return status; +} + +int pvr2_encoder_stop(struct pvr2_hdw *hdw) +{ + int status; + + /* mask all interrupts */ + pvr2_write_register(hdw, 0x0048, 0xffffffff); + + switch (hdw->active_stream_type) { + case pvr2_config_vbi: + status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3, + 0x01,0x01,0x14); + break; + case pvr2_config_mpeg: + status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3, + 0x01,0,0x13); + break; + default: /* Unhandled cases for now */ + status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3, + 0x01,0,0x13); + break; + } + + return status; +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-encoder.h b/drivers/media/usb/pvrusb2/pvrusb2-encoder.h new file mode 100644 index 0000000000..bcb5ab65f4 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-encoder.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#ifndef __PVRUSB2_ENCODER_H +#define __PVRUSB2_ENCODER_H + +struct pvr2_hdw; + +int pvr2_encoder_adjust(struct pvr2_hdw *); +int pvr2_encoder_configure(struct pvr2_hdw *); +int pvr2_encoder_start(struct pvr2_hdw *); +int pvr2_encoder_stop(struct pvr2_hdw *); + +#endif /* __PVRUSB2_ENCODER_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-fx2-cmd.h b/drivers/media/usb/pvrusb2/pvrusb2-fx2-cmd.h new file mode 100644 index 0000000000..e54aa42b41 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-fx2-cmd.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2007 Michael Krufky <mkrufky@linuxtv.org> + */ + +#ifndef _PVRUSB2_FX2_CMD_H_ +#define _PVRUSB2_FX2_CMD_H_ + +#define FX2CMD_MEM_WRITE_DWORD 0x01u +#define FX2CMD_MEM_READ_DWORD 0x02u + +#define FX2CMD_HCW_ZILOG_RESET 0x10u /* 1=reset 0=release */ + +#define FX2CMD_MEM_READ_64BYTES 0x28u + +#define FX2CMD_REG_WRITE 0x04u +#define FX2CMD_REG_READ 0x05u +#define FX2CMD_MEMSEL 0x06u + +#define FX2CMD_I2C_WRITE 0x08u +#define FX2CMD_I2C_READ 0x09u + +#define FX2CMD_GET_USB_SPEED 0x0bu + +#define FX2CMD_STREAMING_ON 0x36u +#define FX2CMD_STREAMING_OFF 0x37u + +#define FX2CMD_FWPOST1 0x52u + +/* These 2 only exist on Model 160xxx */ +#define FX2CMD_HCW_DEMOD_RESET_PIN 0xd4u +#define FX2CMD_HCW_MAKO_SLEEP_PIN 0xd5u + +#define FX2CMD_POWER_OFF 0xdcu +#define FX2CMD_POWER_ON 0xdeu + +#define FX2CMD_DEEP_RESET 0xddu + +#define FX2CMD_GET_EEPROM_ADDR 0xebu +#define FX2CMD_GET_IR_CODE 0xecu + +#define FX2CMD_HCW_DEMOD_RESETIN 0xf0u +#define FX2CMD_HCW_DTV_STREAMING_ON 0xf1u +#define FX2CMD_HCW_DTV_STREAMING_OFF 0xf2u + +#define FX2CMD_ONAIR_DTV_STREAMING_ON 0xa0u +#define FX2CMD_ONAIR_DTV_STREAMING_OFF 0xa1u +#define FX2CMD_ONAIR_DTV_POWER_ON 0xa2u +#define FX2CMD_ONAIR_DTV_POWER_OFF 0xa3u + +#endif /* _PVRUSB2_FX2_CMD_H_ */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h b/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h new file mode 100644 index 0000000000..7c998ca616 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h @@ -0,0 +1,381 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_HDW_INTERNAL_H +#define __PVRUSB2_HDW_INTERNAL_H + +/* + + This header sets up all the internal structures and definitions needed to + track and coordinate the driver's interaction with the hardware. ONLY + source files which actually implement part of that whole circus should be + including this header. Higher levels, like the external layers to the + various public APIs (V4L, sysfs, etc) should NOT ever include this + private, internal header. This means that pvrusb2-hdw, pvrusb2-encoder, + etc will include this, but pvrusb2-v4l should not. + +*/ + +#include <linux/videodev2.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> +#include <linux/mutex.h> +#include "pvrusb2-hdw.h" +#include "pvrusb2-io.h" +#include <media/v4l2-device.h> +#include <media/drv-intf/cx2341x.h> +#include <media/i2c/ir-kbd-i2c.h> +#include "pvrusb2-devattr.h" + +/* Legal values for PVR2_CID_HSM */ +#define PVR2_CVAL_HSM_FAIL 0 +#define PVR2_CVAL_HSM_FULL 1 +#define PVR2_CVAL_HSM_HIGH 2 + +#define PVR2_VID_ENDPOINT 0x84 +#define PVR2_UNK_ENDPOINT 0x86 /* maybe raw yuv ? */ +#define PVR2_VBI_ENDPOINT 0x88 + +#define PVR2_CTL_BUFFSIZE 64 + +#define FREQTABLE_SIZE 500 + +#define LOCK_TAKE(x) do { mutex_lock(&x##_mutex); x##_held = !0; } while (0) +#define LOCK_GIVE(x) do { x##_held = 0; mutex_unlock(&x##_mutex); } while (0) + +typedef int (*pvr2_ctlf_is_dirty)(struct pvr2_ctrl *); +typedef void (*pvr2_ctlf_clear_dirty)(struct pvr2_ctrl *); +typedef int (*pvr2_ctlf_check_value)(struct pvr2_ctrl *,int); +typedef int (*pvr2_ctlf_get_value)(struct pvr2_ctrl *,int *); +typedef int (*pvr2_ctlf_set_value)(struct pvr2_ctrl *,int msk,int val); +typedef int (*pvr2_ctlf_val_to_sym)(struct pvr2_ctrl *,int msk,int val, + char *,unsigned int,unsigned int *); +typedef int (*pvr2_ctlf_sym_to_val)(struct pvr2_ctrl *, + const char *,unsigned int, + int *mskp,int *valp); +typedef unsigned int (*pvr2_ctlf_get_v4lflags)(struct pvr2_ctrl *); + +/* This structure describes a specific control. A table of these is set up + in pvrusb2-hdw.c. */ +struct pvr2_ctl_info { + /* Control's name suitable for use as an identifier */ + const char *name; + + /* Short description of control */ + const char *desc; + + /* Control's implementation */ + pvr2_ctlf_get_value get_value; /* Get its value */ + pvr2_ctlf_get_value get_def_value; /* Get its default value */ + pvr2_ctlf_get_value get_min_value; /* Get minimum allowed value */ + pvr2_ctlf_get_value get_max_value; /* Get maximum allowed value */ + pvr2_ctlf_set_value set_value; /* Set its value */ + pvr2_ctlf_check_value check_value; /* Check that value is valid */ + pvr2_ctlf_val_to_sym val_to_sym; /* Custom convert value->symbol */ + pvr2_ctlf_sym_to_val sym_to_val; /* Custom convert symbol->value */ + pvr2_ctlf_is_dirty is_dirty; /* Return true if dirty */ + pvr2_ctlf_clear_dirty clear_dirty; /* Clear dirty state */ + pvr2_ctlf_get_v4lflags get_v4lflags;/* Retrieve v4l flags */ + + /* Control's type (int, enum, bitmask) */ + enum pvr2_ctl_type type; + + /* Associated V4L control ID, if any */ + int v4l_id; + + /* Associated driver internal ID, if any */ + int internal_id; + + /* Don't implicitly initialize this control's value */ + int skip_init; + + /* Starting value for this control */ + int default_value; + + /* Type-specific control information */ + union { + struct { /* Integer control */ + long min_value; /* lower limit */ + long max_value; /* upper limit */ + } type_int; + struct { /* enumerated control */ + unsigned int count; /* enum value count */ + const char * const *value_names; /* symbol names */ + } type_enum; + struct { /* bitmask control */ + unsigned int valid_bits; /* bits in use */ + const char **bit_names; /* symbol name/bit */ + } type_bitmask; + } def; +}; + + +/* Same as pvr2_ctl_info, but includes storage for the control description */ +#define PVR2_CTLD_INFO_DESC_SIZE 32 +struct pvr2_ctld_info { + struct pvr2_ctl_info info; + char desc[PVR2_CTLD_INFO_DESC_SIZE]; +}; + +struct pvr2_ctrl { + const struct pvr2_ctl_info *info; + struct pvr2_hdw *hdw; +}; + + + +/* Disposition of firmware1 loading situation */ +#define FW1_STATE_UNKNOWN 0 +#define FW1_STATE_MISSING 1 +#define FW1_STATE_FAILED 2 +#define FW1_STATE_RELOAD 3 +#define FW1_STATE_OK 4 + +/* What state the device is in if it is a hybrid */ +#define PVR2_PATHWAY_UNKNOWN 0 +#define PVR2_PATHWAY_ANALOG 1 +#define PVR2_PATHWAY_DIGITAL 2 + +typedef int (*pvr2_i2c_func)(struct pvr2_hdw *,u8,u8 *,u16,u8 *, u16); +#define PVR2_I2C_FUNC_CNT 128 + +/* This structure contains all state data directly needed to + manipulate the hardware (as opposed to complying with a kernel + interface) */ +struct pvr2_hdw { + /* Underlying USB device handle */ + struct usb_device *usb_dev; + struct usb_interface *usb_intf; + + /* Our handle into the v4l2 sub-device architecture */ + struct v4l2_device v4l2_dev; + /* Device description, anything that must adjust behavior based on + device specific info will use information held here. */ + const struct pvr2_device_desc *hdw_desc; + + /* Kernel worker thread handling */ + struct work_struct workpoll; /* Update driver state */ + + /* Video spigot */ + struct pvr2_stream *vid_stream; + + /* Mutex for all hardware state control */ + struct mutex big_lock_mutex; + int big_lock_held; /* For debugging */ + + /* This is a simple string which identifies the instance of this + driver. It is unique within the set of existing devices, but + there is no attempt to keep the name consistent with the same + physical device each time. */ + char name[32]; + + /* This is a simple string which identifies the physical device + instance itself - if possible. (If not possible, then it is + based on the specific driver instance, similar to name above.) + The idea here is that userspace might hopefully be able to use + this recognize specific tuners. It will encode a serial number, + if available. */ + char identifier[32]; + + /* I2C stuff */ + struct i2c_adapter i2c_adap; + struct i2c_algorithm i2c_algo; + pvr2_i2c_func i2c_func[PVR2_I2C_FUNC_CNT]; + int i2c_cx25840_hack_state; + int i2c_linked; + + /* IR related */ + unsigned int ir_scheme_active; /* IR scheme as seen from the outside */ + struct IR_i2c_init_data ir_init_data; /* params passed to IR modules */ + + /* Frequency table */ + unsigned int freqTable[FREQTABLE_SIZE]; + unsigned int freqProgSlot; + + /* Stuff for handling low level control interaction with device */ + struct mutex ctl_lock_mutex; + int ctl_lock_held; /* For debugging */ + struct urb *ctl_write_urb; + struct urb *ctl_read_urb; + unsigned char *ctl_write_buffer; + unsigned char *ctl_read_buffer; + int ctl_write_pend_flag; + int ctl_read_pend_flag; + int ctl_timeout_flag; + struct completion ctl_done; + unsigned char cmd_buffer[PVR2_CTL_BUFFSIZE]; + int cmd_debug_state; // Low level command debugging info + unsigned char cmd_debug_code; // + unsigned int cmd_debug_write_len; // + unsigned int cmd_debug_read_len; // + + /* Bits of state that describe what is going on with various parts + of the driver. */ + int state_pathway_ok; /* Pathway config is ok */ + int state_encoder_ok; /* Encoder is operational */ + int state_encoder_run; /* Encoder is running */ + int state_encoder_config; /* Encoder is configured */ + int state_encoder_waitok; /* Encoder pre-wait done */ + int state_encoder_runok; /* Encoder has run for >= .25 sec */ + int state_decoder_run; /* Decoder is running */ + int state_decoder_ready; /* Decoder is stabilized & streamable */ + int state_usbstream_run; /* FX2 is streaming */ + int state_decoder_quiescent; /* Decoder idle for minimal interval */ + int state_pipeline_config; /* Pipeline is configured */ + int state_pipeline_req; /* Somebody wants to stream */ + int state_pipeline_pause; /* Pipeline must be paused */ + int state_pipeline_idle; /* Pipeline not running */ + + /* This is the master state of the driver. It is the combined + result of other bits of state. Examining this will indicate the + overall state of the driver. Values here are one of + PVR2_STATE_xxxx */ + unsigned int master_state; + + /* True if device led is currently on */ + int led_on; + + /* True if states must be re-evaluated */ + int state_stale; + + void (*state_func)(void *); + void *state_data; + + /* Timer for measuring required decoder settling time before we're + allowed to fire it up again. */ + struct timer_list quiescent_timer; + + /* Timer for measuring decoder stabilization time, which is the + amount of time we need to let the decoder run before we can + trust its output (otherwise the encoder might see garbage and + then fail to start correctly). */ + struct timer_list decoder_stabilization_timer; + + /* Timer for measuring encoder pre-wait time */ + struct timer_list encoder_wait_timer; + + /* Timer for measuring encoder minimum run time */ + struct timer_list encoder_run_timer; + + /* Place to block while waiting for state changes */ + wait_queue_head_t state_wait_data; + + + int force_dirty; /* consider all controls dirty if true */ + int flag_ok; /* device in known good state */ + int flag_modulefail; /* true if at least one module failed to load */ + int flag_disconnected; /* flag_ok == 0 due to disconnect */ + int flag_init_ok; /* true if structure is fully initialized */ + int fw1_state; /* current situation with fw1 */ + int pathway_state; /* one of PVR2_PATHWAY_xxx */ + int flag_decoder_missed;/* We've noticed missing decoder */ + int flag_tripped; /* Indicates overall failure to start */ + + unsigned int decoder_client_id; + + // CPU firmware info (used to help find / save firmware data) + char *fw_buffer; + unsigned int fw_size; + int fw_cpu_flag; /* True if we are dealing with the CPU */ + + /* Tuner / frequency control stuff */ + unsigned int tuner_type; + int tuner_updated; + unsigned int freqValTelevision; /* Current freq for tv mode */ + unsigned int freqValRadio; /* Current freq for radio mode */ + unsigned int freqSlotTelevision; /* Current slot for tv mode */ + unsigned int freqSlotRadio; /* Current slot for radio mode */ + unsigned int freqSelector; /* 0=radio 1=television */ + int freqDirty; + + /* Current tuner info - this information is polled from the I2C bus */ + struct v4l2_tuner tuner_signal_info; + int tuner_signal_stale; + + /* Cropping capability info */ + struct v4l2_cropcap cropcap_info; + int cropcap_stale; + + /* Video standard handling */ + v4l2_std_id std_mask_eeprom; // Hardware supported selections + v4l2_std_id std_mask_avail; // Which standards we may select from + v4l2_std_id std_mask_cur; // Currently selected standard(s) + int std_enum_cur; // selected standard enumeration value + int std_dirty; // True if std_mask_cur has changed + struct pvr2_ctl_info std_info_enum; + struct pvr2_ctl_info std_info_avail; + struct pvr2_ctl_info std_info_cur; + struct pvr2_ctl_info std_info_detect; + + // Generated string names, one per actual V4L2 standard + const char *std_mask_ptrs[32]; + char std_mask_names[32][16]; + + int unit_number; /* ID for driver instance */ + unsigned long serial_number; /* ID for hardware itself */ + + char bus_info[32]; /* Bus location info */ + + /* Minor numbers used by v4l logic (yes, this is a hack, as there + should be no v4l junk here). Probably a better way to do this. */ + int v4l_minor_number_video; + int v4l_minor_number_vbi; + int v4l_minor_number_radio; + + /* Bit mask of PVR2_CVAL_INPUT choices which are valid for the hardware */ + unsigned int input_avail_mask; + /* Bit mask of PVR2_CVAL_INPUT choices which are currently allowed */ + unsigned int input_allowed_mask; + + /* Location of eeprom or a negative number if none */ + int eeprom_addr; + + enum pvr2_config active_stream_type; + enum pvr2_config desired_stream_type; + + /* Control state needed for cx2341x module */ + struct cx2341x_mpeg_params enc_cur_state; + struct cx2341x_mpeg_params enc_ctl_state; + /* True if an encoder attribute has changed */ + int enc_stale; + /* True if an unsafe encoder attribute has changed */ + int enc_unsafe_stale; + /* True if enc_cur_state is valid */ + int enc_cur_valid; + + /* Control state */ +#define VCREATE_DATA(lab) int lab##_val; int lab##_dirty + VCREATE_DATA(brightness); + VCREATE_DATA(contrast); + VCREATE_DATA(saturation); + VCREATE_DATA(hue); + VCREATE_DATA(volume); + VCREATE_DATA(balance); + VCREATE_DATA(bass); + VCREATE_DATA(treble); + VCREATE_DATA(mute); + VCREATE_DATA(cropl); + VCREATE_DATA(cropt); + VCREATE_DATA(cropw); + VCREATE_DATA(croph); + VCREATE_DATA(input); + VCREATE_DATA(audiomode); + VCREATE_DATA(res_hor); + VCREATE_DATA(res_ver); + VCREATE_DATA(srate); +#undef VCREATE_DATA + + struct pvr2_ctld_info *mpeg_ctrl_info; + + struct pvr2_ctrl *controls; + unsigned int control_cnt; +}; + +/* This function gets the current frequency */ +unsigned long pvr2_hdw_get_cur_freq(struct pvr2_hdw *); + +void pvr2_hdw_status_poll(struct pvr2_hdw *); + +#endif /* __PVRUSB2_HDW_INTERNAL_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c new file mode 100644 index 0000000000..29cc207194 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c @@ -0,0 +1,5146 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ + +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/firmware.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/tuner.h> +#include "pvrusb2.h" +#include "pvrusb2-std.h" +#include "pvrusb2-util.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-i2c-core.h" +#include "pvrusb2-eeprom.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-encoder.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-fx2-cmd.h" +#include "pvrusb2-wm8775.h" +#include "pvrusb2-video-v4l.h" +#include "pvrusb2-cx2584x-v4l.h" +#include "pvrusb2-cs53l32a.h" +#include "pvrusb2-audio.h" + +#define TV_MIN_FREQ 55250000L +#define TV_MAX_FREQ 850000000L + +/* This defines a minimum interval that the decoder must remain quiet + before we are allowed to start it running. */ +#define TIME_MSEC_DECODER_WAIT 50 + +/* This defines a minimum interval that the decoder must be allowed to run + before we can safely begin using its streaming output. */ +#define TIME_MSEC_DECODER_STABILIZATION_WAIT 300 + +/* This defines a minimum interval that the encoder must remain quiet + before we are allowed to configure it. */ +#define TIME_MSEC_ENCODER_WAIT 50 + +/* This defines the minimum interval that the encoder must successfully run + before we consider that the encoder has run at least once since its + firmware has been loaded. This measurement is in important for cases + where we can't do something until we know that the encoder has been run + at least once. */ +#define TIME_MSEC_ENCODER_OK 250 + +static struct pvr2_hdw *unit_pointers[PVR_NUM] = {[ 0 ... PVR_NUM-1 ] = NULL}; +static DEFINE_MUTEX(pvr2_unit_mtx); + +static int ctlchg; +static int procreload; +static int tuner[PVR_NUM] = { [0 ... PVR_NUM-1] = -1 }; +static int tolerance[PVR_NUM] = { [0 ... PVR_NUM-1] = 0 }; +static int video_std[PVR_NUM] = { [0 ... PVR_NUM-1] = 0 }; +static int init_pause_msec; + +module_param(ctlchg, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(ctlchg, "0=optimize ctl change 1=always accept new ctl value"); +module_param(init_pause_msec, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(init_pause_msec, "hardware initialization settling delay"); +module_param(procreload, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(procreload, + "Attempt init failure recovery with firmware reload"); +module_param_array(tuner, int, NULL, 0444); +MODULE_PARM_DESC(tuner,"specify installed tuner type"); +module_param_array(video_std, int, NULL, 0444); +MODULE_PARM_DESC(video_std,"specify initial video standard"); +module_param_array(tolerance, int, NULL, 0444); +MODULE_PARM_DESC(tolerance,"specify stream error tolerance"); + +/* US Broadcast channel 3 (61.25 MHz), to help with testing */ +static int default_tv_freq = 61250000L; +/* 104.3 MHz, a usable FM station for my area */ +static int default_radio_freq = 104300000L; + +module_param_named(tv_freq, default_tv_freq, int, 0444); +MODULE_PARM_DESC(tv_freq, "specify initial television frequency"); +module_param_named(radio_freq, default_radio_freq, int, 0444); +MODULE_PARM_DESC(radio_freq, "specify initial radio frequency"); + +#define PVR2_CTL_WRITE_ENDPOINT 0x01 +#define PVR2_CTL_READ_ENDPOINT 0x81 + +#define PVR2_GPIO_IN 0x9008 +#define PVR2_GPIO_OUT 0x900c +#define PVR2_GPIO_DIR 0x9020 + +#define trace_firmware(...) pvr2_trace(PVR2_TRACE_FIRMWARE,__VA_ARGS__) + +#define PVR2_FIRMWARE_ENDPOINT 0x02 + +/* size of a firmware chunk */ +#define FIRMWARE_CHUNK_SIZE 0x2000 + +typedef void (*pvr2_subdev_update_func)(struct pvr2_hdw *, + struct v4l2_subdev *); + +static const pvr2_subdev_update_func pvr2_module_update_functions[] = { + [PVR2_CLIENT_ID_WM8775] = pvr2_wm8775_subdev_update, + [PVR2_CLIENT_ID_SAA7115] = pvr2_saa7115_subdev_update, + [PVR2_CLIENT_ID_MSP3400] = pvr2_msp3400_subdev_update, + [PVR2_CLIENT_ID_CX25840] = pvr2_cx25840_subdev_update, + [PVR2_CLIENT_ID_CS53L32A] = pvr2_cs53l32a_subdev_update, +}; + +static const char *module_names[] = { + [PVR2_CLIENT_ID_MSP3400] = "msp3400", + [PVR2_CLIENT_ID_CX25840] = "cx25840", + [PVR2_CLIENT_ID_SAA7115] = "saa7115", + [PVR2_CLIENT_ID_TUNER] = "tuner", + [PVR2_CLIENT_ID_DEMOD] = "tuner", + [PVR2_CLIENT_ID_CS53L32A] = "cs53l32a", + [PVR2_CLIENT_ID_WM8775] = "wm8775", +}; + + +static const unsigned char *module_i2c_addresses[] = { + [PVR2_CLIENT_ID_TUNER] = "\x60\x61\x62\x63", + [PVR2_CLIENT_ID_DEMOD] = "\x43", + [PVR2_CLIENT_ID_MSP3400] = "\x40", + [PVR2_CLIENT_ID_SAA7115] = "\x21", + [PVR2_CLIENT_ID_WM8775] = "\x1b", + [PVR2_CLIENT_ID_CX25840] = "\x44", + [PVR2_CLIENT_ID_CS53L32A] = "\x11", +}; + + +static const char *ir_scheme_names[] = { + [PVR2_IR_SCHEME_NONE] = "none", + [PVR2_IR_SCHEME_29XXX] = "29xxx", + [PVR2_IR_SCHEME_24XXX] = "24xxx (29xxx emulation)", + [PVR2_IR_SCHEME_24XXX_MCE] = "24xxx (MCE device)", + [PVR2_IR_SCHEME_ZILOG] = "Zilog", +}; + + +/* Define the list of additional controls we'll dynamically construct based + on query of the cx2341x module. */ +struct pvr2_mpeg_ids { + const char *strid; + int id; +}; +static const struct pvr2_mpeg_ids mpeg_ids[] = { + { + .strid = "audio_layer", + .id = V4L2_CID_MPEG_AUDIO_ENCODING, + },{ + .strid = "audio_bitrate", + .id = V4L2_CID_MPEG_AUDIO_L2_BITRATE, + },{ + /* Already using audio_mode elsewhere :-( */ + .strid = "mpeg_audio_mode", + .id = V4L2_CID_MPEG_AUDIO_MODE, + },{ + .strid = "mpeg_audio_mode_extension", + .id = V4L2_CID_MPEG_AUDIO_MODE_EXTENSION, + },{ + .strid = "audio_emphasis", + .id = V4L2_CID_MPEG_AUDIO_EMPHASIS, + },{ + .strid = "audio_crc", + .id = V4L2_CID_MPEG_AUDIO_CRC, + },{ + .strid = "video_aspect", + .id = V4L2_CID_MPEG_VIDEO_ASPECT, + },{ + .strid = "video_b_frames", + .id = V4L2_CID_MPEG_VIDEO_B_FRAMES, + },{ + .strid = "video_gop_size", + .id = V4L2_CID_MPEG_VIDEO_GOP_SIZE, + },{ + .strid = "video_gop_closure", + .id = V4L2_CID_MPEG_VIDEO_GOP_CLOSURE, + },{ + .strid = "video_bitrate_mode", + .id = V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + },{ + .strid = "video_bitrate", + .id = V4L2_CID_MPEG_VIDEO_BITRATE, + },{ + .strid = "video_bitrate_peak", + .id = V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, + },{ + .strid = "video_temporal_decimation", + .id = V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION, + },{ + .strid = "stream_type", + .id = V4L2_CID_MPEG_STREAM_TYPE, + },{ + .strid = "video_spatial_filter_mode", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE, + },{ + .strid = "video_spatial_filter", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER, + },{ + .strid = "video_luma_spatial_filter_type", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE, + },{ + .strid = "video_chroma_spatial_filter_type", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE, + },{ + .strid = "video_temporal_filter_mode", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE, + },{ + .strid = "video_temporal_filter", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER, + },{ + .strid = "video_median_filter_type", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE, + },{ + .strid = "video_luma_median_filter_top", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP, + },{ + .strid = "video_luma_median_filter_bottom", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM, + },{ + .strid = "video_chroma_median_filter_top", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP, + },{ + .strid = "video_chroma_median_filter_bottom", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM, + } +}; +#define MPEGDEF_COUNT ARRAY_SIZE(mpeg_ids) + + +static const char *control_values_srate[] = { + [V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100] = "44.1 kHz", + [V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000] = "48 kHz", + [V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000] = "32 kHz", +}; + + + +static const char *control_values_input[] = { + [PVR2_CVAL_INPUT_TV] = "television", /*xawtv needs this name*/ + [PVR2_CVAL_INPUT_DTV] = "dtv", + [PVR2_CVAL_INPUT_RADIO] = "radio", + [PVR2_CVAL_INPUT_SVIDEO] = "s-video", + [PVR2_CVAL_INPUT_COMPOSITE] = "composite", +}; + + +static const char *control_values_audiomode[] = { + [V4L2_TUNER_MODE_MONO] = "Mono", + [V4L2_TUNER_MODE_STEREO] = "Stereo", + [V4L2_TUNER_MODE_LANG1] = "Lang1", + [V4L2_TUNER_MODE_LANG2] = "Lang2", + [V4L2_TUNER_MODE_LANG1_LANG2] = "Lang1+Lang2", +}; + + +static const char *control_values_hsm[] = { + [PVR2_CVAL_HSM_FAIL] = "Fail", + [PVR2_CVAL_HSM_HIGH] = "High", + [PVR2_CVAL_HSM_FULL] = "Full", +}; + + +static const char *pvr2_state_names[] = { + [PVR2_STATE_NONE] = "none", + [PVR2_STATE_DEAD] = "dead", + [PVR2_STATE_COLD] = "cold", + [PVR2_STATE_WARM] = "warm", + [PVR2_STATE_ERROR] = "error", + [PVR2_STATE_READY] = "ready", + [PVR2_STATE_RUN] = "run", +}; + + +struct pvr2_fx2cmd_descdef { + unsigned char id; + unsigned char *desc; +}; + +static const struct pvr2_fx2cmd_descdef pvr2_fx2cmd_desc[] = { + {FX2CMD_MEM_WRITE_DWORD, "write encoder dword"}, + {FX2CMD_MEM_READ_DWORD, "read encoder dword"}, + {FX2CMD_HCW_ZILOG_RESET, "zilog IR reset control"}, + {FX2CMD_MEM_READ_64BYTES, "read encoder 64bytes"}, + {FX2CMD_REG_WRITE, "write encoder register"}, + {FX2CMD_REG_READ, "read encoder register"}, + {FX2CMD_MEMSEL, "encoder memsel"}, + {FX2CMD_I2C_WRITE, "i2c write"}, + {FX2CMD_I2C_READ, "i2c read"}, + {FX2CMD_GET_USB_SPEED, "get USB speed"}, + {FX2CMD_STREAMING_ON, "stream on"}, + {FX2CMD_STREAMING_OFF, "stream off"}, + {FX2CMD_FWPOST1, "fwpost1"}, + {FX2CMD_POWER_OFF, "power off"}, + {FX2CMD_POWER_ON, "power on"}, + {FX2CMD_DEEP_RESET, "deep reset"}, + {FX2CMD_GET_EEPROM_ADDR, "get rom addr"}, + {FX2CMD_GET_IR_CODE, "get IR code"}, + {FX2CMD_HCW_DEMOD_RESETIN, "hcw demod resetin"}, + {FX2CMD_HCW_DTV_STREAMING_ON, "hcw dtv stream on"}, + {FX2CMD_HCW_DTV_STREAMING_OFF, "hcw dtv stream off"}, + {FX2CMD_ONAIR_DTV_STREAMING_ON, "onair dtv stream on"}, + {FX2CMD_ONAIR_DTV_STREAMING_OFF, "onair dtv stream off"}, + {FX2CMD_ONAIR_DTV_POWER_ON, "onair dtv power on"}, + {FX2CMD_ONAIR_DTV_POWER_OFF, "onair dtv power off"}, + {FX2CMD_HCW_DEMOD_RESET_PIN, "hcw demod reset pin"}, + {FX2CMD_HCW_MAKO_SLEEP_PIN, "hcw mako sleep pin"}, +}; + + +static int pvr2_hdw_set_input(struct pvr2_hdw *hdw,int v); +static void pvr2_hdw_state_sched(struct pvr2_hdw *); +static int pvr2_hdw_state_eval(struct pvr2_hdw *); +static void pvr2_hdw_set_cur_freq(struct pvr2_hdw *,unsigned long); +static void pvr2_hdw_worker_poll(struct work_struct *work); +static int pvr2_hdw_wait(struct pvr2_hdw *,int state); +static int pvr2_hdw_untrip_unlocked(struct pvr2_hdw *); +static void pvr2_hdw_state_log_state(struct pvr2_hdw *); +static int pvr2_hdw_cmd_usbstream(struct pvr2_hdw *hdw,int runFl); +static int pvr2_hdw_commit_setup(struct pvr2_hdw *hdw); +static int pvr2_hdw_get_eeprom_addr(struct pvr2_hdw *hdw); +static void pvr2_hdw_quiescent_timeout(struct timer_list *); +static void pvr2_hdw_decoder_stabilization_timeout(struct timer_list *); +static void pvr2_hdw_encoder_wait_timeout(struct timer_list *); +static void pvr2_hdw_encoder_run_timeout(struct timer_list *); +static int pvr2_issue_simple_cmd(struct pvr2_hdw *,u32); +static int pvr2_send_request_ex(struct pvr2_hdw *hdw, + unsigned int timeout,int probe_fl, + void *write_data,unsigned int write_len, + void *read_data,unsigned int read_len); +static int pvr2_hdw_check_cropcap(struct pvr2_hdw *hdw); +static v4l2_std_id pvr2_hdw_get_detected_std(struct pvr2_hdw *hdw); + +static void trace_stbit(const char *name,int val) +{ + pvr2_trace(PVR2_TRACE_STBITS, + "State bit %s <-- %s", + name,(val ? "true" : "false")); +} + +static int ctrl_channelfreq_get(struct pvr2_ctrl *cptr,int *vp) +{ + struct pvr2_hdw *hdw = cptr->hdw; + if ((hdw->freqProgSlot > 0) && (hdw->freqProgSlot <= FREQTABLE_SIZE)) { + *vp = hdw->freqTable[hdw->freqProgSlot-1]; + } else { + *vp = 0; + } + return 0; +} + +static int ctrl_channelfreq_set(struct pvr2_ctrl *cptr,int m,int v) +{ + struct pvr2_hdw *hdw = cptr->hdw; + unsigned int slotId = hdw->freqProgSlot; + if ((slotId > 0) && (slotId <= FREQTABLE_SIZE)) { + hdw->freqTable[slotId-1] = v; + /* Handle side effects correctly - if we're tuned to this + slot, then forgot the slot id relation since the stored + frequency has been changed. */ + if (hdw->freqSelector) { + if (hdw->freqSlotRadio == slotId) { + hdw->freqSlotRadio = 0; + } + } else { + if (hdw->freqSlotTelevision == slotId) { + hdw->freqSlotTelevision = 0; + } + } + } + return 0; +} + +static int ctrl_channelprog_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->freqProgSlot; + return 0; +} + +static int ctrl_channelprog_set(struct pvr2_ctrl *cptr,int m,int v) +{ + struct pvr2_hdw *hdw = cptr->hdw; + if ((v >= 0) && (v <= FREQTABLE_SIZE)) { + hdw->freqProgSlot = v; + } + return 0; +} + +static int ctrl_channel_get(struct pvr2_ctrl *cptr,int *vp) +{ + struct pvr2_hdw *hdw = cptr->hdw; + *vp = hdw->freqSelector ? hdw->freqSlotRadio : hdw->freqSlotTelevision; + return 0; +} + +static int ctrl_channel_set(struct pvr2_ctrl *cptr,int m,int slotId) +{ + unsigned freq = 0; + struct pvr2_hdw *hdw = cptr->hdw; + if ((slotId < 0) || (slotId > FREQTABLE_SIZE)) return 0; + if (slotId > 0) { + freq = hdw->freqTable[slotId-1]; + if (!freq) return 0; + pvr2_hdw_set_cur_freq(hdw,freq); + } + if (hdw->freqSelector) { + hdw->freqSlotRadio = slotId; + } else { + hdw->freqSlotTelevision = slotId; + } + return 0; +} + +static int ctrl_freq_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = pvr2_hdw_get_cur_freq(cptr->hdw); + return 0; +} + +static int ctrl_freq_is_dirty(struct pvr2_ctrl *cptr) +{ + return cptr->hdw->freqDirty != 0; +} + +static void ctrl_freq_clear_dirty(struct pvr2_ctrl *cptr) +{ + cptr->hdw->freqDirty = 0; +} + +static int ctrl_freq_set(struct pvr2_ctrl *cptr,int m,int v) +{ + pvr2_hdw_set_cur_freq(cptr->hdw,v); + return 0; +} + +static int ctrl_cropl_min_get(struct pvr2_ctrl *cptr, int *left) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *left = cap->bounds.left; + return 0; +} + +static int ctrl_cropl_max_get(struct pvr2_ctrl *cptr, int *left) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *left = cap->bounds.left; + if (cap->bounds.width > cptr->hdw->cropw_val) { + *left += cap->bounds.width - cptr->hdw->cropw_val; + } + return 0; +} + +static int ctrl_cropt_min_get(struct pvr2_ctrl *cptr, int *top) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *top = cap->bounds.top; + return 0; +} + +static int ctrl_cropt_max_get(struct pvr2_ctrl *cptr, int *top) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *top = cap->bounds.top; + if (cap->bounds.height > cptr->hdw->croph_val) { + *top += cap->bounds.height - cptr->hdw->croph_val; + } + return 0; +} + +static int ctrl_cropw_max_get(struct pvr2_ctrl *cptr, int *width) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat, bleftend, cleft; + + stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + bleftend = cap->bounds.left+cap->bounds.width; + cleft = cptr->hdw->cropl_val; + + *width = cleft < bleftend ? bleftend-cleft : 0; + return 0; +} + +static int ctrl_croph_max_get(struct pvr2_ctrl *cptr, int *height) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat, btopend, ctop; + + stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + btopend = cap->bounds.top+cap->bounds.height; + ctop = cptr->hdw->cropt_val; + + *height = ctop < btopend ? btopend-ctop : 0; + return 0; +} + +static int ctrl_get_cropcapbl(struct pvr2_ctrl *cptr, int *val) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *val = cap->bounds.left; + return 0; +} + +static int ctrl_get_cropcapbt(struct pvr2_ctrl *cptr, int *val) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *val = cap->bounds.top; + return 0; +} + +static int ctrl_get_cropcapbw(struct pvr2_ctrl *cptr, int *val) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *val = cap->bounds.width; + return 0; +} + +static int ctrl_get_cropcapbh(struct pvr2_ctrl *cptr, int *val) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *val = cap->bounds.height; + return 0; +} + +static int ctrl_get_cropcapdl(struct pvr2_ctrl *cptr, int *val) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *val = cap->defrect.left; + return 0; +} + +static int ctrl_get_cropcapdt(struct pvr2_ctrl *cptr, int *val) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *val = cap->defrect.top; + return 0; +} + +static int ctrl_get_cropcapdw(struct pvr2_ctrl *cptr, int *val) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *val = cap->defrect.width; + return 0; +} + +static int ctrl_get_cropcapdh(struct pvr2_ctrl *cptr, int *val) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *val = cap->defrect.height; + return 0; +} + +static int ctrl_get_cropcappan(struct pvr2_ctrl *cptr, int *val) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *val = cap->pixelaspect.numerator; + return 0; +} + +static int ctrl_get_cropcappad(struct pvr2_ctrl *cptr, int *val) +{ + struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info; + int stat = pvr2_hdw_check_cropcap(cptr->hdw); + if (stat != 0) { + return stat; + } + *val = cap->pixelaspect.denominator; + return 0; +} + +static int ctrl_vres_max_get(struct pvr2_ctrl *cptr,int *vp) +{ + /* Actual maximum depends on the video standard in effect. */ + if (cptr->hdw->std_mask_cur & V4L2_STD_525_60) { + *vp = 480; + } else { + *vp = 576; + } + return 0; +} + +static int ctrl_vres_min_get(struct pvr2_ctrl *cptr,int *vp) +{ + /* Actual minimum depends on device digitizer type. */ + if (cptr->hdw->hdw_desc->flag_has_cx25840) { + *vp = 75; + } else { + *vp = 17; + } + return 0; +} + +static int ctrl_get_input(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->input_val; + return 0; +} + +static int ctrl_check_input(struct pvr2_ctrl *cptr,int v) +{ + if (v < 0 || v > PVR2_CVAL_INPUT_MAX) + return 0; + return ((1UL << v) & cptr->hdw->input_allowed_mask) != 0; +} + +static int ctrl_set_input(struct pvr2_ctrl *cptr,int m,int v) +{ + return pvr2_hdw_set_input(cptr->hdw,v); +} + +static int ctrl_isdirty_input(struct pvr2_ctrl *cptr) +{ + return cptr->hdw->input_dirty != 0; +} + +static void ctrl_cleardirty_input(struct pvr2_ctrl *cptr) +{ + cptr->hdw->input_dirty = 0; +} + + +static int ctrl_freq_max_get(struct pvr2_ctrl *cptr, int *vp) +{ + unsigned long fv; + struct pvr2_hdw *hdw = cptr->hdw; + if (hdw->tuner_signal_stale) { + pvr2_hdw_status_poll(hdw); + } + fv = hdw->tuner_signal_info.rangehigh; + if (!fv) { + /* Safety fallback */ + *vp = TV_MAX_FREQ; + return 0; + } + if (hdw->tuner_signal_info.capability & V4L2_TUNER_CAP_LOW) { + fv = (fv * 125) / 2; + } else { + fv = fv * 62500; + } + *vp = fv; + return 0; +} + +static int ctrl_freq_min_get(struct pvr2_ctrl *cptr, int *vp) +{ + unsigned long fv; + struct pvr2_hdw *hdw = cptr->hdw; + if (hdw->tuner_signal_stale) { + pvr2_hdw_status_poll(hdw); + } + fv = hdw->tuner_signal_info.rangelow; + if (!fv) { + /* Safety fallback */ + *vp = TV_MIN_FREQ; + return 0; + } + if (hdw->tuner_signal_info.capability & V4L2_TUNER_CAP_LOW) { + fv = (fv * 125) / 2; + } else { + fv = fv * 62500; + } + *vp = fv; + return 0; +} + +static int ctrl_cx2341x_is_dirty(struct pvr2_ctrl *cptr) +{ + return cptr->hdw->enc_stale != 0; +} + +static void ctrl_cx2341x_clear_dirty(struct pvr2_ctrl *cptr) +{ + cptr->hdw->enc_stale = 0; + cptr->hdw->enc_unsafe_stale = 0; +} + +static int ctrl_cx2341x_get(struct pvr2_ctrl *cptr,int *vp) +{ + int ret; + struct v4l2_ext_controls cs; + struct v4l2_ext_control c1; + memset(&cs,0,sizeof(cs)); + memset(&c1,0,sizeof(c1)); + cs.controls = &c1; + cs.count = 1; + c1.id = cptr->info->v4l_id; + ret = cx2341x_ext_ctrls(&cptr->hdw->enc_ctl_state, 0, &cs, + VIDIOC_G_EXT_CTRLS); + if (ret) return ret; + *vp = c1.value; + return 0; +} + +static int ctrl_cx2341x_set(struct pvr2_ctrl *cptr,int m,int v) +{ + int ret; + struct pvr2_hdw *hdw = cptr->hdw; + struct v4l2_ext_controls cs; + struct v4l2_ext_control c1; + memset(&cs,0,sizeof(cs)); + memset(&c1,0,sizeof(c1)); + cs.controls = &c1; + cs.count = 1; + c1.id = cptr->info->v4l_id; + c1.value = v; + ret = cx2341x_ext_ctrls(&hdw->enc_ctl_state, + hdw->state_encoder_run, &cs, + VIDIOC_S_EXT_CTRLS); + if (ret == -EBUSY) { + /* Oops. cx2341x is telling us it's not safe to change + this control while we're capturing. Make a note of this + fact so that the pipeline will be stopped the next time + controls are committed. Then go on ahead and store this + change anyway. */ + ret = cx2341x_ext_ctrls(&hdw->enc_ctl_state, + 0, &cs, + VIDIOC_S_EXT_CTRLS); + if (!ret) hdw->enc_unsafe_stale = !0; + } + if (ret) return ret; + hdw->enc_stale = !0; + return 0; +} + +static unsigned int ctrl_cx2341x_getv4lflags(struct pvr2_ctrl *cptr) +{ + struct v4l2_queryctrl qctrl = {}; + struct pvr2_ctl_info *info; + qctrl.id = cptr->info->v4l_id; + cx2341x_ctrl_query(&cptr->hdw->enc_ctl_state,&qctrl); + /* Strip out the const so we can adjust a function pointer. It's + OK to do this here because we know this is a dynamically created + control, so the underlying storage for the info pointer is (a) + private to us, and (b) not in read-only storage. Either we do + this or we significantly complicate the underlying control + implementation. */ + info = (struct pvr2_ctl_info *)(cptr->info); + if (qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) { + if (info->set_value) { + info->set_value = NULL; + } + } else { + if (!(info->set_value)) { + info->set_value = ctrl_cx2341x_set; + } + } + return qctrl.flags; +} + +static int ctrl_streamingenabled_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->state_pipeline_req; + return 0; +} + +static int ctrl_masterstate_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->master_state; + return 0; +} + +static int ctrl_hsm_get(struct pvr2_ctrl *cptr,int *vp) +{ + int result = pvr2_hdw_is_hsm(cptr->hdw); + *vp = PVR2_CVAL_HSM_FULL; + if (result < 0) *vp = PVR2_CVAL_HSM_FAIL; + if (result) *vp = PVR2_CVAL_HSM_HIGH; + return 0; +} + +static int ctrl_stddetect_get(struct pvr2_ctrl *cptr, int *vp) +{ + *vp = pvr2_hdw_get_detected_std(cptr->hdw); + return 0; +} + +static int ctrl_stdavail_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->std_mask_avail; + return 0; +} + +static int ctrl_stdavail_set(struct pvr2_ctrl *cptr,int m,int v) +{ + struct pvr2_hdw *hdw = cptr->hdw; + v4l2_std_id ns; + ns = hdw->std_mask_avail; + ns = (ns & ~m) | (v & m); + if (ns == hdw->std_mask_avail) return 0; + hdw->std_mask_avail = ns; + hdw->std_info_cur.def.type_bitmask.valid_bits = hdw->std_mask_avail; + return 0; +} + +static int ctrl_std_val_to_sym(struct pvr2_ctrl *cptr,int msk,int val, + char *bufPtr,unsigned int bufSize, + unsigned int *len) +{ + *len = pvr2_std_id_to_str(bufPtr,bufSize,msk & val); + return 0; +} + +static int ctrl_std_sym_to_val(struct pvr2_ctrl *cptr, + const char *bufPtr,unsigned int bufSize, + int *mskp,int *valp) +{ + v4l2_std_id id; + if (!pvr2_std_str_to_id(&id, bufPtr, bufSize)) + return -EINVAL; + if (mskp) *mskp = id; + if (valp) *valp = id; + return 0; +} + +static int ctrl_stdcur_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->std_mask_cur; + return 0; +} + +static int ctrl_stdcur_set(struct pvr2_ctrl *cptr,int m,int v) +{ + struct pvr2_hdw *hdw = cptr->hdw; + v4l2_std_id ns; + ns = hdw->std_mask_cur; + ns = (ns & ~m) | (v & m); + if (ns == hdw->std_mask_cur) return 0; + hdw->std_mask_cur = ns; + hdw->std_dirty = !0; + return 0; +} + +static int ctrl_stdcur_is_dirty(struct pvr2_ctrl *cptr) +{ + return cptr->hdw->std_dirty != 0; +} + +static void ctrl_stdcur_clear_dirty(struct pvr2_ctrl *cptr) +{ + cptr->hdw->std_dirty = 0; +} + +static int ctrl_signal_get(struct pvr2_ctrl *cptr,int *vp) +{ + struct pvr2_hdw *hdw = cptr->hdw; + pvr2_hdw_status_poll(hdw); + *vp = hdw->tuner_signal_info.signal; + return 0; +} + +static int ctrl_audio_modes_present_get(struct pvr2_ctrl *cptr,int *vp) +{ + int val = 0; + unsigned int subchan; + struct pvr2_hdw *hdw = cptr->hdw; + pvr2_hdw_status_poll(hdw); + subchan = hdw->tuner_signal_info.rxsubchans; + if (subchan & V4L2_TUNER_SUB_MONO) { + val |= (1 << V4L2_TUNER_MODE_MONO); + } + if (subchan & V4L2_TUNER_SUB_STEREO) { + val |= (1 << V4L2_TUNER_MODE_STEREO); + } + if (subchan & V4L2_TUNER_SUB_LANG1) { + val |= (1 << V4L2_TUNER_MODE_LANG1); + } + if (subchan & V4L2_TUNER_SUB_LANG2) { + val |= (1 << V4L2_TUNER_MODE_LANG2); + } + *vp = val; + return 0; +} + + +#define DEFINT(vmin,vmax) \ + .type = pvr2_ctl_int, \ + .def.type_int.min_value = vmin, \ + .def.type_int.max_value = vmax + +#define DEFENUM(tab) \ + .type = pvr2_ctl_enum, \ + .def.type_enum.count = ARRAY_SIZE(tab), \ + .def.type_enum.value_names = tab + +#define DEFBOOL \ + .type = pvr2_ctl_bool + +#define DEFMASK(msk,tab) \ + .type = pvr2_ctl_bitmask, \ + .def.type_bitmask.valid_bits = msk, \ + .def.type_bitmask.bit_names = tab + +#define DEFREF(vname) \ + .set_value = ctrl_set_##vname, \ + .get_value = ctrl_get_##vname, \ + .is_dirty = ctrl_isdirty_##vname, \ + .clear_dirty = ctrl_cleardirty_##vname + + +#define VCREATE_FUNCS(vname) \ +static int ctrl_get_##vname(struct pvr2_ctrl *cptr,int *vp) \ +{*vp = cptr->hdw->vname##_val; return 0;} \ +static int ctrl_set_##vname(struct pvr2_ctrl *cptr,int m,int v) \ +{cptr->hdw->vname##_val = v; cptr->hdw->vname##_dirty = !0; return 0;} \ +static int ctrl_isdirty_##vname(struct pvr2_ctrl *cptr) \ +{return cptr->hdw->vname##_dirty != 0;} \ +static void ctrl_cleardirty_##vname(struct pvr2_ctrl *cptr) \ +{cptr->hdw->vname##_dirty = 0;} + +VCREATE_FUNCS(brightness) +VCREATE_FUNCS(contrast) +VCREATE_FUNCS(saturation) +VCREATE_FUNCS(hue) +VCREATE_FUNCS(volume) +VCREATE_FUNCS(balance) +VCREATE_FUNCS(bass) +VCREATE_FUNCS(treble) +VCREATE_FUNCS(mute) +VCREATE_FUNCS(cropl) +VCREATE_FUNCS(cropt) +VCREATE_FUNCS(cropw) +VCREATE_FUNCS(croph) +VCREATE_FUNCS(audiomode) +VCREATE_FUNCS(res_hor) +VCREATE_FUNCS(res_ver) +VCREATE_FUNCS(srate) + +/* Table definition of all controls which can be manipulated */ +static const struct pvr2_ctl_info control_defs[] = { + { + .v4l_id = V4L2_CID_BRIGHTNESS, + .desc = "Brightness", + .name = "brightness", + .default_value = 128, + DEFREF(brightness), + DEFINT(0,255), + },{ + .v4l_id = V4L2_CID_CONTRAST, + .desc = "Contrast", + .name = "contrast", + .default_value = 68, + DEFREF(contrast), + DEFINT(0,127), + },{ + .v4l_id = V4L2_CID_SATURATION, + .desc = "Saturation", + .name = "saturation", + .default_value = 64, + DEFREF(saturation), + DEFINT(0,127), + },{ + .v4l_id = V4L2_CID_HUE, + .desc = "Hue", + .name = "hue", + .default_value = 0, + DEFREF(hue), + DEFINT(-128,127), + },{ + .v4l_id = V4L2_CID_AUDIO_VOLUME, + .desc = "Volume", + .name = "volume", + .default_value = 62000, + DEFREF(volume), + DEFINT(0,65535), + },{ + .v4l_id = V4L2_CID_AUDIO_BALANCE, + .desc = "Balance", + .name = "balance", + .default_value = 0, + DEFREF(balance), + DEFINT(-32768,32767), + },{ + .v4l_id = V4L2_CID_AUDIO_BASS, + .desc = "Bass", + .name = "bass", + .default_value = 0, + DEFREF(bass), + DEFINT(-32768,32767), + },{ + .v4l_id = V4L2_CID_AUDIO_TREBLE, + .desc = "Treble", + .name = "treble", + .default_value = 0, + DEFREF(treble), + DEFINT(-32768,32767), + },{ + .v4l_id = V4L2_CID_AUDIO_MUTE, + .desc = "Mute", + .name = "mute", + .default_value = 0, + DEFREF(mute), + DEFBOOL, + }, { + .desc = "Capture crop left margin", + .name = "crop_left", + .internal_id = PVR2_CID_CROPL, + .default_value = 0, + DEFREF(cropl), + DEFINT(-129, 340), + .get_min_value = ctrl_cropl_min_get, + .get_max_value = ctrl_cropl_max_get, + .get_def_value = ctrl_get_cropcapdl, + }, { + .desc = "Capture crop top margin", + .name = "crop_top", + .internal_id = PVR2_CID_CROPT, + .default_value = 0, + DEFREF(cropt), + DEFINT(-35, 544), + .get_min_value = ctrl_cropt_min_get, + .get_max_value = ctrl_cropt_max_get, + .get_def_value = ctrl_get_cropcapdt, + }, { + .desc = "Capture crop width", + .name = "crop_width", + .internal_id = PVR2_CID_CROPW, + .default_value = 720, + DEFREF(cropw), + DEFINT(0, 864), + .get_max_value = ctrl_cropw_max_get, + .get_def_value = ctrl_get_cropcapdw, + }, { + .desc = "Capture crop height", + .name = "crop_height", + .internal_id = PVR2_CID_CROPH, + .default_value = 480, + DEFREF(croph), + DEFINT(0, 576), + .get_max_value = ctrl_croph_max_get, + .get_def_value = ctrl_get_cropcapdh, + }, { + .desc = "Capture capability pixel aspect numerator", + .name = "cropcap_pixel_numerator", + .internal_id = PVR2_CID_CROPCAPPAN, + .get_value = ctrl_get_cropcappan, + }, { + .desc = "Capture capability pixel aspect denominator", + .name = "cropcap_pixel_denominator", + .internal_id = PVR2_CID_CROPCAPPAD, + .get_value = ctrl_get_cropcappad, + }, { + .desc = "Capture capability bounds top", + .name = "cropcap_bounds_top", + .internal_id = PVR2_CID_CROPCAPBT, + .get_value = ctrl_get_cropcapbt, + }, { + .desc = "Capture capability bounds left", + .name = "cropcap_bounds_left", + .internal_id = PVR2_CID_CROPCAPBL, + .get_value = ctrl_get_cropcapbl, + }, { + .desc = "Capture capability bounds width", + .name = "cropcap_bounds_width", + .internal_id = PVR2_CID_CROPCAPBW, + .get_value = ctrl_get_cropcapbw, + }, { + .desc = "Capture capability bounds height", + .name = "cropcap_bounds_height", + .internal_id = PVR2_CID_CROPCAPBH, + .get_value = ctrl_get_cropcapbh, + },{ + .desc = "Video Source", + .name = "input", + .internal_id = PVR2_CID_INPUT, + .default_value = PVR2_CVAL_INPUT_TV, + .check_value = ctrl_check_input, + DEFREF(input), + DEFENUM(control_values_input), + },{ + .desc = "Audio Mode", + .name = "audio_mode", + .internal_id = PVR2_CID_AUDIOMODE, + .default_value = V4L2_TUNER_MODE_STEREO, + DEFREF(audiomode), + DEFENUM(control_values_audiomode), + },{ + .desc = "Horizontal capture resolution", + .name = "resolution_hor", + .internal_id = PVR2_CID_HRES, + .default_value = 720, + DEFREF(res_hor), + DEFINT(19,720), + },{ + .desc = "Vertical capture resolution", + .name = "resolution_ver", + .internal_id = PVR2_CID_VRES, + .default_value = 480, + DEFREF(res_ver), + DEFINT(17,576), + /* Hook in check for video standard and adjust maximum + depending on the standard. */ + .get_max_value = ctrl_vres_max_get, + .get_min_value = ctrl_vres_min_get, + },{ + .v4l_id = V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ, + .default_value = V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000, + .desc = "Audio Sampling Frequency", + .name = "srate", + DEFREF(srate), + DEFENUM(control_values_srate), + },{ + .desc = "Tuner Frequency (Hz)", + .name = "frequency", + .internal_id = PVR2_CID_FREQUENCY, + .default_value = 0, + .set_value = ctrl_freq_set, + .get_value = ctrl_freq_get, + .is_dirty = ctrl_freq_is_dirty, + .clear_dirty = ctrl_freq_clear_dirty, + DEFINT(0,0), + /* Hook in check for input value (tv/radio) and adjust + max/min values accordingly */ + .get_max_value = ctrl_freq_max_get, + .get_min_value = ctrl_freq_min_get, + },{ + .desc = "Channel", + .name = "channel", + .set_value = ctrl_channel_set, + .get_value = ctrl_channel_get, + DEFINT(0,FREQTABLE_SIZE), + },{ + .desc = "Channel Program Frequency", + .name = "freq_table_value", + .set_value = ctrl_channelfreq_set, + .get_value = ctrl_channelfreq_get, + DEFINT(0,0), + /* Hook in check for input value (tv/radio) and adjust + max/min values accordingly */ + .get_max_value = ctrl_freq_max_get, + .get_min_value = ctrl_freq_min_get, + },{ + .desc = "Channel Program ID", + .name = "freq_table_channel", + .set_value = ctrl_channelprog_set, + .get_value = ctrl_channelprog_get, + DEFINT(0,FREQTABLE_SIZE), + },{ + .desc = "Streaming Enabled", + .name = "streaming_enabled", + .get_value = ctrl_streamingenabled_get, + DEFBOOL, + },{ + .desc = "USB Speed", + .name = "usb_speed", + .get_value = ctrl_hsm_get, + DEFENUM(control_values_hsm), + },{ + .desc = "Master State", + .name = "master_state", + .get_value = ctrl_masterstate_get, + DEFENUM(pvr2_state_names), + },{ + .desc = "Signal Present", + .name = "signal_present", + .get_value = ctrl_signal_get, + DEFINT(0,65535), + },{ + .desc = "Audio Modes Present", + .name = "audio_modes_present", + .get_value = ctrl_audio_modes_present_get, + /* For this type we "borrow" the V4L2_TUNER_MODE enum from + v4l. Nothing outside of this module cares about this, + but I reuse it in order to also reuse the + control_values_audiomode string table. */ + DEFMASK(((1 << V4L2_TUNER_MODE_MONO)| + (1 << V4L2_TUNER_MODE_STEREO)| + (1 << V4L2_TUNER_MODE_LANG1)| + (1 << V4L2_TUNER_MODE_LANG2)), + control_values_audiomode), + },{ + .desc = "Video Standards Available Mask", + .name = "video_standard_mask_available", + .internal_id = PVR2_CID_STDAVAIL, + .skip_init = !0, + .get_value = ctrl_stdavail_get, + .set_value = ctrl_stdavail_set, + .val_to_sym = ctrl_std_val_to_sym, + .sym_to_val = ctrl_std_sym_to_val, + .type = pvr2_ctl_bitmask, + },{ + .desc = "Video Standards In Use Mask", + .name = "video_standard_mask_active", + .internal_id = PVR2_CID_STDCUR, + .skip_init = !0, + .get_value = ctrl_stdcur_get, + .set_value = ctrl_stdcur_set, + .is_dirty = ctrl_stdcur_is_dirty, + .clear_dirty = ctrl_stdcur_clear_dirty, + .val_to_sym = ctrl_std_val_to_sym, + .sym_to_val = ctrl_std_sym_to_val, + .type = pvr2_ctl_bitmask, + },{ + .desc = "Video Standards Detected Mask", + .name = "video_standard_mask_detected", + .internal_id = PVR2_CID_STDDETECT, + .skip_init = !0, + .get_value = ctrl_stddetect_get, + .val_to_sym = ctrl_std_val_to_sym, + .sym_to_val = ctrl_std_sym_to_val, + .type = pvr2_ctl_bitmask, + } +}; + +#define CTRLDEF_COUNT ARRAY_SIZE(control_defs) + + +const char *pvr2_config_get_name(enum pvr2_config cfg) +{ + switch (cfg) { + case pvr2_config_empty: return "empty"; + case pvr2_config_mpeg: return "mpeg"; + case pvr2_config_vbi: return "vbi"; + case pvr2_config_pcm: return "pcm"; + case pvr2_config_rawvideo: return "raw video"; + } + return "<unknown>"; +} + + +struct usb_device *pvr2_hdw_get_dev(struct pvr2_hdw *hdw) +{ + return hdw->usb_dev; +} + + +unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *hdw) +{ + return hdw->serial_number; +} + + +const char *pvr2_hdw_get_bus_info(struct pvr2_hdw *hdw) +{ + return hdw->bus_info; +} + + +const char *pvr2_hdw_get_device_identifier(struct pvr2_hdw *hdw) +{ + return hdw->identifier; +} + + +unsigned long pvr2_hdw_get_cur_freq(struct pvr2_hdw *hdw) +{ + return hdw->freqSelector ? hdw->freqValTelevision : hdw->freqValRadio; +} + +/* Set the currently tuned frequency and account for all possible + driver-core side effects of this action. */ +static void pvr2_hdw_set_cur_freq(struct pvr2_hdw *hdw,unsigned long val) +{ + if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) { + if (hdw->freqSelector) { + /* Swing over to radio frequency selection */ + hdw->freqSelector = 0; + hdw->freqDirty = !0; + } + if (hdw->freqValRadio != val) { + hdw->freqValRadio = val; + hdw->freqSlotRadio = 0; + hdw->freqDirty = !0; + } + } else { + if (!(hdw->freqSelector)) { + /* Swing over to television frequency selection */ + hdw->freqSelector = 1; + hdw->freqDirty = !0; + } + if (hdw->freqValTelevision != val) { + hdw->freqValTelevision = val; + hdw->freqSlotTelevision = 0; + hdw->freqDirty = !0; + } + } +} + +int pvr2_hdw_get_unit_number(struct pvr2_hdw *hdw) +{ + return hdw->unit_number; +} + + +/* Attempt to locate one of the given set of files. Messages are logged + appropriate to what has been found. The return value will be 0 or + greater on success (it will be the index of the file name found) and + fw_entry will be filled in. Otherwise a negative error is returned on + failure. If the return value is -ENOENT then no viable firmware file + could be located. */ +static int pvr2_locate_firmware(struct pvr2_hdw *hdw, + const struct firmware **fw_entry, + const char *fwtypename, + unsigned int fwcount, + const char *fwnames[]) +{ + unsigned int idx; + int ret = -EINVAL; + for (idx = 0; idx < fwcount; idx++) { + ret = request_firmware(fw_entry, + fwnames[idx], + &hdw->usb_dev->dev); + if (!ret) { + trace_firmware("Located %s firmware: %s; uploading...", + fwtypename, + fwnames[idx]); + return idx; + } + if (ret == -ENOENT) continue; + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "request_firmware fatal error with code=%d",ret); + return ret; + } + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "***WARNING*** Device %s firmware seems to be missing.", + fwtypename); + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Did you install the pvrusb2 firmware files in their proper location?"); + if (fwcount == 1) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "request_firmware unable to locate %s file %s", + fwtypename,fwnames[0]); + } else { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "request_firmware unable to locate one of the following %s files:", + fwtypename); + for (idx = 0; idx < fwcount; idx++) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "request_firmware: Failed to find %s", + fwnames[idx]); + } + } + return ret; +} + + +/* + * pvr2_upload_firmware1(). + * + * Send the 8051 firmware to the device. After the upload, arrange for + * device to re-enumerate. + * + * NOTE : the pointer to the firmware data given by request_firmware() + * is not suitable for an usb transaction. + * + */ +static int pvr2_upload_firmware1(struct pvr2_hdw *hdw) +{ + const struct firmware *fw_entry = NULL; + void *fw_ptr; + unsigned int pipe; + unsigned int fwsize; + int ret; + u16 address; + + if (!hdw->hdw_desc->fx2_firmware.cnt) { + hdw->fw1_state = FW1_STATE_OK; + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Connected device type defines no firmware to upload; ignoring firmware"); + return -ENOTTY; + } + + hdw->fw1_state = FW1_STATE_FAILED; // default result + + trace_firmware("pvr2_upload_firmware1"); + + ret = pvr2_locate_firmware(hdw,&fw_entry,"fx2 controller", + hdw->hdw_desc->fx2_firmware.cnt, + hdw->hdw_desc->fx2_firmware.lst); + if (ret < 0) { + if (ret == -ENOENT) hdw->fw1_state = FW1_STATE_MISSING; + return ret; + } + + usb_clear_halt(hdw->usb_dev, usb_sndbulkpipe(hdw->usb_dev, 0 & 0x7f)); + + pipe = usb_sndctrlpipe(hdw->usb_dev, 0); + fwsize = fw_entry->size; + + if ((fwsize != 0x2000) && + (!(hdw->hdw_desc->flag_fx2_16kb && (fwsize == 0x4000)))) { + if (hdw->hdw_desc->flag_fx2_16kb) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Wrong fx2 firmware size (expected 8192 or 16384, got %u)", + fwsize); + } else { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Wrong fx2 firmware size (expected 8192, got %u)", + fwsize); + } + release_firmware(fw_entry); + return -ENOMEM; + } + + fw_ptr = kmalloc(0x800, GFP_KERNEL); + if (fw_ptr == NULL){ + release_firmware(fw_entry); + return -ENOMEM; + } + + /* We have to hold the CPU during firmware upload. */ + pvr2_hdw_cpureset_assert(hdw,1); + + /* upload the firmware to address 0000-1fff in 2048 (=0x800) bytes + chunk. */ + + ret = 0; + for (address = 0; address < fwsize; address += 0x800) { + memcpy(fw_ptr, fw_entry->data + address, 0x800); + ret += usb_control_msg(hdw->usb_dev, pipe, 0xa0, 0x40, address, + 0, fw_ptr, 0x800, 1000); + } + + trace_firmware("Upload done, releasing device's CPU"); + + /* Now release the CPU. It will disconnect and reconnect later. */ + pvr2_hdw_cpureset_assert(hdw,0); + + kfree(fw_ptr); + release_firmware(fw_entry); + + trace_firmware("Upload done (%d bytes sent)",ret); + + /* We should have written fwsize bytes */ + if (ret == fwsize) { + hdw->fw1_state = FW1_STATE_RELOAD; + return 0; + } + + return -EIO; +} + + +/* + * pvr2_upload_firmware2() + * + * This uploads encoder firmware on endpoint 2. + * + */ + +int pvr2_upload_firmware2(struct pvr2_hdw *hdw) +{ + const struct firmware *fw_entry = NULL; + void *fw_ptr; + unsigned int pipe, fw_len, fw_done, bcnt, icnt; + int actual_length; + int ret = 0; + int fwidx; + static const char *fw_files[] = { + CX2341X_FIRM_ENC_FILENAME, + }; + + if (hdw->hdw_desc->flag_skip_cx23416_firmware) { + return 0; + } + + trace_firmware("pvr2_upload_firmware2"); + + ret = pvr2_locate_firmware(hdw,&fw_entry,"encoder", + ARRAY_SIZE(fw_files), fw_files); + if (ret < 0) return ret; + fwidx = ret; + ret = 0; + /* Since we're about to completely reinitialize the encoder, + invalidate our cached copy of its configuration state. Next + time we configure the encoder, then we'll fully configure it. */ + hdw->enc_cur_valid = 0; + + /* Encoder is about to be reset so note that as far as we're + concerned now, the encoder has never been run. */ + del_timer_sync(&hdw->encoder_run_timer); + if (hdw->state_encoder_runok) { + hdw->state_encoder_runok = 0; + trace_stbit("state_encoder_runok",hdw->state_encoder_runok); + } + + /* First prepare firmware loading */ + ret |= pvr2_write_register(hdw, 0x0048, 0xffffffff); /*interrupt mask*/ + ret |= pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000088); /*gpio dir*/ + ret |= pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000008); /*gpio output state*/ + ret |= pvr2_hdw_cmd_deep_reset(hdw); + ret |= pvr2_write_register(hdw, 0xa064, 0x00000000); /*APU command*/ + ret |= pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000408); /*gpio dir*/ + ret |= pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000008); /*gpio output state*/ + ret |= pvr2_write_register(hdw, 0x9058, 0xffffffed); /*VPU ctrl*/ + ret |= pvr2_write_register(hdw, 0x9054, 0xfffffffd); /*reset hw blocks*/ + ret |= pvr2_write_register(hdw, 0x07f8, 0x80000800); /*encoder SDRAM refresh*/ + ret |= pvr2_write_register(hdw, 0x07fc, 0x0000001a); /*encoder SDRAM pre-charge*/ + ret |= pvr2_write_register(hdw, 0x0700, 0x00000000); /*I2C clock*/ + ret |= pvr2_write_register(hdw, 0xaa00, 0x00000000); /*unknown*/ + ret |= pvr2_write_register(hdw, 0xaa04, 0x00057810); /*unknown*/ + ret |= pvr2_write_register(hdw, 0xaa10, 0x00148500); /*unknown*/ + ret |= pvr2_write_register(hdw, 0xaa18, 0x00840000); /*unknown*/ + ret |= pvr2_issue_simple_cmd(hdw,FX2CMD_FWPOST1); + ret |= pvr2_issue_simple_cmd(hdw,FX2CMD_MEMSEL | (1 << 8) | (0 << 16)); + + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "firmware2 upload prep failed, ret=%d",ret); + release_firmware(fw_entry); + goto done; + } + + /* Now send firmware */ + + fw_len = fw_entry->size; + + if (fw_len % sizeof(u32)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "size of %s firmware must be a multiple of %zu bytes", + fw_files[fwidx],sizeof(u32)); + release_firmware(fw_entry); + ret = -EINVAL; + goto done; + } + + fw_ptr = kmalloc(FIRMWARE_CHUNK_SIZE, GFP_KERNEL); + if (fw_ptr == NULL){ + release_firmware(fw_entry); + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "failed to allocate memory for firmware2 upload"); + ret = -ENOMEM; + goto done; + } + + pipe = usb_sndbulkpipe(hdw->usb_dev, PVR2_FIRMWARE_ENDPOINT); + + fw_done = 0; + for (fw_done = 0; fw_done < fw_len;) { + bcnt = fw_len - fw_done; + if (bcnt > FIRMWARE_CHUNK_SIZE) bcnt = FIRMWARE_CHUNK_SIZE; + memcpy(fw_ptr, fw_entry->data + fw_done, bcnt); + /* Usbsnoop log shows that we must swap bytes... */ + /* Some background info: The data being swapped here is a + firmware image destined for the mpeg encoder chip that + lives at the other end of a USB endpoint. The encoder + chip always talks in 32 bit chunks and its storage is + organized into 32 bit words. However from the file + system to the encoder chip everything is purely a byte + stream. The firmware file's contents are always 32 bit + swapped from what the encoder expects. Thus the need + always exists to swap the bytes regardless of the endian + type of the host processor and therefore swab32() makes + the most sense. */ + for (icnt = 0; icnt < bcnt/4 ; icnt++) + ((u32 *)fw_ptr)[icnt] = swab32(((u32 *)fw_ptr)[icnt]); + + ret |= usb_bulk_msg(hdw->usb_dev, pipe, fw_ptr,bcnt, + &actual_length, 1000); + ret |= (actual_length != bcnt); + if (ret) break; + fw_done += bcnt; + } + + trace_firmware("upload of %s : %i / %i ", + fw_files[fwidx],fw_done,fw_len); + + kfree(fw_ptr); + release_firmware(fw_entry); + + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "firmware2 upload transfer failure"); + goto done; + } + + /* Finish upload */ + + ret |= pvr2_write_register(hdw, 0x9054, 0xffffffff); /*reset hw blocks*/ + ret |= pvr2_write_register(hdw, 0x9058, 0xffffffe8); /*VPU ctrl*/ + ret |= pvr2_issue_simple_cmd(hdw,FX2CMD_MEMSEL | (1 << 8) | (0 << 16)); + + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "firmware2 upload post-proc failure"); + } + + done: + if (hdw->hdw_desc->signal_routing_scheme == + PVR2_ROUTING_SCHEME_GOTVIEW) { + /* Ensure that GPIO 11 is set to output for GOTVIEW + hardware. */ + pvr2_hdw_gpio_chg_dir(hdw,(1 << 11),~0); + } + return ret; +} + + +static const char *pvr2_get_state_name(unsigned int st) +{ + if (st < ARRAY_SIZE(pvr2_state_names)) { + return pvr2_state_names[st]; + } + return "???"; +} + +static int pvr2_decoder_enable(struct pvr2_hdw *hdw,int enablefl) +{ + /* Even though we really only care about the video decoder chip at + this point, we'll broadcast stream on/off to all sub-devices + anyway, just in case somebody else wants to hear the + command... */ + pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 stream=%s", + (enablefl ? "on" : "off")); + v4l2_device_call_all(&hdw->v4l2_dev, 0, video, s_stream, enablefl); + v4l2_device_call_all(&hdw->v4l2_dev, 0, audio, s_stream, enablefl); + if (hdw->decoder_client_id) { + /* We get here if the encoder has been noticed. Otherwise + we'll issue a warning to the user (which should + normally never happen). */ + return 0; + } + if (!hdw->flag_decoder_missed) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "***WARNING*** No decoder present"); + hdw->flag_decoder_missed = !0; + trace_stbit("flag_decoder_missed", + hdw->flag_decoder_missed); + } + return -EIO; +} + + +int pvr2_hdw_get_state(struct pvr2_hdw *hdw) +{ + return hdw->master_state; +} + + +static int pvr2_hdw_untrip_unlocked(struct pvr2_hdw *hdw) +{ + if (!hdw->flag_tripped) return 0; + hdw->flag_tripped = 0; + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Clearing driver error status"); + return !0; +} + + +int pvr2_hdw_untrip(struct pvr2_hdw *hdw) +{ + int fl; + LOCK_TAKE(hdw->big_lock); do { + fl = pvr2_hdw_untrip_unlocked(hdw); + } while (0); LOCK_GIVE(hdw->big_lock); + if (fl) pvr2_hdw_state_sched(hdw); + return 0; +} + + + + +int pvr2_hdw_get_streaming(struct pvr2_hdw *hdw) +{ + return hdw->state_pipeline_req != 0; +} + + +int pvr2_hdw_set_streaming(struct pvr2_hdw *hdw,int enable_flag) +{ + int ret,st; + LOCK_TAKE(hdw->big_lock); + pvr2_hdw_untrip_unlocked(hdw); + if (!enable_flag != !hdw->state_pipeline_req) { + hdw->state_pipeline_req = enable_flag != 0; + pvr2_trace(PVR2_TRACE_START_STOP, + "/*--TRACE_STREAM--*/ %s", + enable_flag ? "enable" : "disable"); + } + pvr2_hdw_state_sched(hdw); + LOCK_GIVE(hdw->big_lock); + if ((ret = pvr2_hdw_wait(hdw,0)) < 0) return ret; + if (enable_flag) { + while ((st = hdw->master_state) != PVR2_STATE_RUN) { + if (st != PVR2_STATE_READY) return -EIO; + if ((ret = pvr2_hdw_wait(hdw,st)) < 0) return ret; + } + } + return 0; +} + + +int pvr2_hdw_set_stream_type(struct pvr2_hdw *hdw,enum pvr2_config config) +{ + int fl; + LOCK_TAKE(hdw->big_lock); + if ((fl = (hdw->desired_stream_type != config)) != 0) { + hdw->desired_stream_type = config; + hdw->state_pipeline_config = 0; + trace_stbit("state_pipeline_config", + hdw->state_pipeline_config); + pvr2_hdw_state_sched(hdw); + } + LOCK_GIVE(hdw->big_lock); + if (fl) return 0; + return pvr2_hdw_wait(hdw,0); +} + + +static int get_default_tuner_type(struct pvr2_hdw *hdw) +{ + int unit_number = hdw->unit_number; + int tp = -1; + if ((unit_number >= 0) && (unit_number < PVR_NUM)) { + tp = tuner[unit_number]; + } + if (tp < 0) return -EINVAL; + hdw->tuner_type = tp; + hdw->tuner_updated = !0; + return 0; +} + + +static v4l2_std_id get_default_standard(struct pvr2_hdw *hdw) +{ + int unit_number = hdw->unit_number; + int tp = 0; + if ((unit_number >= 0) && (unit_number < PVR_NUM)) { + tp = video_std[unit_number]; + if (tp) return tp; + } + return 0; +} + + +static unsigned int get_default_error_tolerance(struct pvr2_hdw *hdw) +{ + int unit_number = hdw->unit_number; + int tp = 0; + if ((unit_number >= 0) && (unit_number < PVR_NUM)) { + tp = tolerance[unit_number]; + } + return tp; +} + + +static int pvr2_hdw_check_firmware(struct pvr2_hdw *hdw) +{ + /* Try a harmless request to fetch the eeprom's address over + endpoint 1. See what happens. Only the full FX2 image can + respond to this. If this probe fails then likely the FX2 + firmware needs be loaded. */ + int result; + LOCK_TAKE(hdw->ctl_lock); do { + hdw->cmd_buffer[0] = FX2CMD_GET_EEPROM_ADDR; + result = pvr2_send_request_ex(hdw,HZ*1,!0, + hdw->cmd_buffer,1, + hdw->cmd_buffer,1); + if (result < 0) break; + } while(0); LOCK_GIVE(hdw->ctl_lock); + if (result) { + pvr2_trace(PVR2_TRACE_INIT, + "Probe of device endpoint 1 result status %d", + result); + } else { + pvr2_trace(PVR2_TRACE_INIT, + "Probe of device endpoint 1 succeeded"); + } + return result == 0; +} + +struct pvr2_std_hack { + v4l2_std_id pat; /* Pattern to match */ + v4l2_std_id msk; /* Which bits we care about */ + v4l2_std_id std; /* What additional standards or default to set */ +}; + +/* This data structure labels specific combinations of standards from + tveeprom that we'll try to recognize. If we recognize one, then assume + a specified default standard to use. This is here because tveeprom only + tells us about available standards not the intended default standard (if + any) for the device in question. We guess the default based on what has + been reported as available. Note that this is only for guessing a + default - which can always be overridden explicitly - and if the user + has otherwise named a default then that default will always be used in + place of this table. */ +static const struct pvr2_std_hack std_eeprom_maps[] = { + { /* PAL(B/G) */ + .pat = V4L2_STD_B|V4L2_STD_GH, + .std = V4L2_STD_PAL_B|V4L2_STD_PAL_B1|V4L2_STD_PAL_G, + }, + { /* NTSC(M) */ + .pat = V4L2_STD_MN, + .std = V4L2_STD_NTSC_M, + }, + { /* PAL(I) */ + .pat = V4L2_STD_PAL_I, + .std = V4L2_STD_PAL_I, + }, + { /* SECAM(L/L') */ + .pat = V4L2_STD_SECAM_L|V4L2_STD_SECAM_LC, + .std = V4L2_STD_SECAM_L|V4L2_STD_SECAM_LC, + }, + { /* PAL(D/D1/K) */ + .pat = V4L2_STD_DK, + .std = V4L2_STD_PAL_D|V4L2_STD_PAL_D1|V4L2_STD_PAL_K, + }, +}; + +static void pvr2_hdw_setup_std(struct pvr2_hdw *hdw) +{ + char buf[40]; + unsigned int bcnt; + v4l2_std_id std1,std2,std3; + + std1 = get_default_standard(hdw); + std3 = std1 ? 0 : hdw->hdw_desc->default_std_mask; + + bcnt = pvr2_std_id_to_str(buf,sizeof(buf),hdw->std_mask_eeprom); + pvr2_trace(PVR2_TRACE_STD, + "Supported video standard(s) reported available in hardware: %.*s", + bcnt,buf); + + hdw->std_mask_avail = hdw->std_mask_eeprom; + + std2 = (std1|std3) & ~hdw->std_mask_avail; + if (std2) { + bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std2); + pvr2_trace(PVR2_TRACE_STD, + "Expanding supported video standards to include: %.*s", + bcnt,buf); + hdw->std_mask_avail |= std2; + } + + hdw->std_info_cur.def.type_bitmask.valid_bits = hdw->std_mask_avail; + + if (std1) { + bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std1); + pvr2_trace(PVR2_TRACE_STD, + "Initial video standard forced to %.*s", + bcnt,buf); + hdw->std_mask_cur = std1; + hdw->std_dirty = !0; + return; + } + if (std3) { + bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std3); + pvr2_trace(PVR2_TRACE_STD, + "Initial video standard (determined by device type): %.*s", + bcnt, buf); + hdw->std_mask_cur = std3; + hdw->std_dirty = !0; + return; + } + + { + unsigned int idx; + for (idx = 0; idx < ARRAY_SIZE(std_eeprom_maps); idx++) { + if (std_eeprom_maps[idx].msk ? + ((std_eeprom_maps[idx].pat ^ + hdw->std_mask_eeprom) & + std_eeprom_maps[idx].msk) : + (std_eeprom_maps[idx].pat != + hdw->std_mask_eeprom)) continue; + bcnt = pvr2_std_id_to_str(buf,sizeof(buf), + std_eeprom_maps[idx].std); + pvr2_trace(PVR2_TRACE_STD, + "Initial video standard guessed as %.*s", + bcnt,buf); + hdw->std_mask_cur = std_eeprom_maps[idx].std; + hdw->std_dirty = !0; + return; + } + } + +} + + +static unsigned int pvr2_copy_i2c_addr_list( + unsigned short *dst, const unsigned char *src, + unsigned int dst_max) +{ + unsigned int cnt = 0; + if (!src) return 0; + while (src[cnt] && (cnt + 1) < dst_max) { + dst[cnt] = src[cnt]; + cnt++; + } + dst[cnt] = I2C_CLIENT_END; + return cnt; +} + + +static void pvr2_hdw_cx25840_vbi_hack(struct pvr2_hdw *hdw) +{ + /* + Mike Isely <isely@pobox.com> 19-Nov-2006 - This bit of nuttiness + for cx25840 causes that module to correctly set up its video + scaling. This is really a problem in the cx25840 module itself, + but we work around it here. The problem has not been seen in + ivtv because there VBI is supported and set up. We don't do VBI + here (at least not yet) and thus we never attempted to even set + it up. + */ + struct v4l2_format fmt; + if (hdw->decoder_client_id != PVR2_CLIENT_ID_CX25840) { + /* We're not using a cx25840 so don't enable the hack */ + return; + } + + pvr2_trace(PVR2_TRACE_INIT, + "Module ID %u: Executing cx25840 VBI hack", + hdw->decoder_client_id); + memset(&fmt, 0, sizeof(fmt)); + fmt.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE; + fmt.fmt.sliced.service_lines[0][21] = V4L2_SLICED_CAPTION_525; + fmt.fmt.sliced.service_lines[1][21] = V4L2_SLICED_CAPTION_525; + v4l2_device_call_all(&hdw->v4l2_dev, hdw->decoder_client_id, + vbi, s_sliced_fmt, &fmt.fmt.sliced); +} + + +static int pvr2_hdw_load_subdev(struct pvr2_hdw *hdw, + const struct pvr2_device_client_desc *cd) +{ + const char *fname; + unsigned char mid; + struct v4l2_subdev *sd; + unsigned int i2ccnt; + const unsigned char *p; + /* Arbitrary count - max # i2c addresses we will probe */ + unsigned short i2caddr[25]; + + mid = cd->module_id; + fname = (mid < ARRAY_SIZE(module_names)) ? module_names[mid] : NULL; + if (!fname) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Module ID %u for device %s has no name? The driver might have a configuration problem.", + mid, + hdw->hdw_desc->description); + return -EINVAL; + } + pvr2_trace(PVR2_TRACE_INIT, + "Module ID %u (%s) for device %s being loaded...", + mid, fname, + hdw->hdw_desc->description); + + i2ccnt = pvr2_copy_i2c_addr_list(i2caddr, cd->i2c_address_list, + ARRAY_SIZE(i2caddr)); + if (!i2ccnt && ((p = (mid < ARRAY_SIZE(module_i2c_addresses)) ? + module_i2c_addresses[mid] : NULL) != NULL)) { + /* Second chance: Try default i2c address list */ + i2ccnt = pvr2_copy_i2c_addr_list(i2caddr, p, + ARRAY_SIZE(i2caddr)); + if (i2ccnt) { + pvr2_trace(PVR2_TRACE_INIT, + "Module ID %u: Using default i2c address list", + mid); + } + } + + if (!i2ccnt) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Module ID %u (%s) for device %s: No i2c addresses. The driver might have a configuration problem.", + mid, fname, hdw->hdw_desc->description); + return -EINVAL; + } + + if (i2ccnt == 1) { + pvr2_trace(PVR2_TRACE_INIT, + "Module ID %u: Setting up with specified i2c address 0x%x", + mid, i2caddr[0]); + sd = v4l2_i2c_new_subdev(&hdw->v4l2_dev, &hdw->i2c_adap, + fname, i2caddr[0], NULL); + } else { + pvr2_trace(PVR2_TRACE_INIT, + "Module ID %u: Setting up with address probe list", + mid); + sd = v4l2_i2c_new_subdev(&hdw->v4l2_dev, &hdw->i2c_adap, + fname, 0, i2caddr); + } + + if (!sd) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Module ID %u (%s) for device %s failed to load. Possible missing sub-device kernel module or initialization failure within module.", + mid, fname, hdw->hdw_desc->description); + return -EIO; + } + + /* Tag this sub-device instance with the module ID we know about. + In other places we'll use that tag to determine if the instance + requires special handling. */ + sd->grp_id = mid; + + pvr2_trace(PVR2_TRACE_INFO, "Attached sub-driver %s", fname); + + + /* client-specific setup... */ + switch (mid) { + case PVR2_CLIENT_ID_CX25840: + case PVR2_CLIENT_ID_SAA7115: + hdw->decoder_client_id = mid; + break; + default: break; + } + + return 0; +} + + +static void pvr2_hdw_load_modules(struct pvr2_hdw *hdw) +{ + unsigned int idx; + const struct pvr2_string_table *cm; + const struct pvr2_device_client_table *ct; + int okFl = !0; + + cm = &hdw->hdw_desc->client_modules; + for (idx = 0; idx < cm->cnt; idx++) { + request_module(cm->lst[idx]); + } + + ct = &hdw->hdw_desc->client_table; + for (idx = 0; idx < ct->cnt; idx++) { + if (pvr2_hdw_load_subdev(hdw, &ct->lst[idx]) < 0) okFl = 0; + } + if (!okFl) { + hdw->flag_modulefail = !0; + pvr2_hdw_render_useless(hdw); + } +} + + +static void pvr2_hdw_setup_low(struct pvr2_hdw *hdw) +{ + int ret; + unsigned int idx; + struct pvr2_ctrl *cptr; + int reloadFl = 0; + if (hdw->hdw_desc->fx2_firmware.cnt) { + if (!reloadFl) { + reloadFl = + (hdw->usb_intf->cur_altsetting->desc.bNumEndpoints + == 0); + if (reloadFl) { + pvr2_trace(PVR2_TRACE_INIT, + "USB endpoint config looks strange; possibly firmware needs to be loaded"); + } + } + if (!reloadFl) { + reloadFl = !pvr2_hdw_check_firmware(hdw); + if (reloadFl) { + pvr2_trace(PVR2_TRACE_INIT, + "Check for FX2 firmware failed; possibly firmware needs to be loaded"); + } + } + if (reloadFl) { + if (pvr2_upload_firmware1(hdw) != 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failure uploading firmware1"); + } + return; + } + } + hdw->fw1_state = FW1_STATE_OK; + + if (!pvr2_hdw_dev_ok(hdw)) return; + + hdw->force_dirty = !0; + + if (!hdw->hdw_desc->flag_no_powerup) { + pvr2_hdw_cmd_powerup(hdw); + if (!pvr2_hdw_dev_ok(hdw)) return; + } + + /* Take the IR chip out of reset, if appropriate */ + if (hdw->ir_scheme_active == PVR2_IR_SCHEME_ZILOG) { + pvr2_issue_simple_cmd(hdw, + FX2CMD_HCW_ZILOG_RESET | + (1 << 8) | + ((0) << 16)); + } + + /* This step MUST happen after the earlier powerup step */ + pvr2_i2c_core_init(hdw); + if (!pvr2_hdw_dev_ok(hdw)) return; + + /* Reset demod only on Hauppauge 160xxx platform */ + if (le16_to_cpu(hdw->usb_dev->descriptor.idVendor) == 0x2040 && + (le16_to_cpu(hdw->usb_dev->descriptor.idProduct) == 0x7502 || + le16_to_cpu(hdw->usb_dev->descriptor.idProduct) == 0x7510)) { + pr_info("%s(): resetting 160xxx demod\n", __func__); + /* TODO: not sure this is proper place to reset once only */ + pvr2_issue_simple_cmd(hdw, + FX2CMD_HCW_DEMOD_RESET_PIN | + (1 << 8) | + ((0) << 16)); + usleep_range(10000, 10500); + pvr2_issue_simple_cmd(hdw, + FX2CMD_HCW_DEMOD_RESET_PIN | + (1 << 8) | + ((1) << 16)); + usleep_range(10000, 10500); + } + + pvr2_hdw_load_modules(hdw); + if (!pvr2_hdw_dev_ok(hdw)) return; + + v4l2_device_call_all(&hdw->v4l2_dev, 0, core, load_fw); + + for (idx = 0; idx < CTRLDEF_COUNT; idx++) { + cptr = hdw->controls + idx; + if (cptr->info->skip_init) continue; + if (!cptr->info->set_value) continue; + cptr->info->set_value(cptr,~0,cptr->info->default_value); + } + + pvr2_hdw_cx25840_vbi_hack(hdw); + + /* Set up special default values for the television and radio + frequencies here. It's not really important what these defaults + are, but I set them to something usable in the Chicago area just + to make driver testing a little easier. */ + + hdw->freqValTelevision = default_tv_freq; + hdw->freqValRadio = default_radio_freq; + + // Do not use pvr2_reset_ctl_endpoints() here. It is not + // thread-safe against the normal pvr2_send_request() mechanism. + // (We should make it thread safe). + + if (hdw->hdw_desc->flag_has_hauppauge_rom) { + ret = pvr2_hdw_get_eeprom_addr(hdw); + if (!pvr2_hdw_dev_ok(hdw)) return; + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Unable to determine location of eeprom, skipping"); + } else { + hdw->eeprom_addr = ret; + pvr2_eeprom_analyze(hdw); + if (!pvr2_hdw_dev_ok(hdw)) return; + } + } else { + hdw->tuner_type = hdw->hdw_desc->default_tuner_type; + hdw->tuner_updated = !0; + hdw->std_mask_eeprom = V4L2_STD_ALL; + } + + if (hdw->serial_number) { + idx = scnprintf(hdw->identifier, sizeof(hdw->identifier) - 1, + "sn-%lu", hdw->serial_number); + } else if (hdw->unit_number >= 0) { + idx = scnprintf(hdw->identifier, sizeof(hdw->identifier) - 1, + "unit-%c", + hdw->unit_number + 'a'); + } else { + idx = scnprintf(hdw->identifier, sizeof(hdw->identifier) - 1, + "unit-??"); + } + hdw->identifier[idx] = 0; + + pvr2_hdw_setup_std(hdw); + + if (!get_default_tuner_type(hdw)) { + pvr2_trace(PVR2_TRACE_INIT, + "pvr2_hdw_setup: Tuner type overridden to %d", + hdw->tuner_type); + } + + + if (!pvr2_hdw_dev_ok(hdw)) return; + + if (hdw->hdw_desc->signal_routing_scheme == + PVR2_ROUTING_SCHEME_GOTVIEW) { + /* Ensure that GPIO 11 is set to output for GOTVIEW + hardware. */ + pvr2_hdw_gpio_chg_dir(hdw,(1 << 11),~0); + } + + pvr2_hdw_commit_setup(hdw); + + hdw->vid_stream = pvr2_stream_create(); + if (!pvr2_hdw_dev_ok(hdw)) return; + pvr2_trace(PVR2_TRACE_INIT, + "pvr2_hdw_setup: video stream is %p",hdw->vid_stream); + if (hdw->vid_stream) { + idx = get_default_error_tolerance(hdw); + if (idx) { + pvr2_trace(PVR2_TRACE_INIT, + "pvr2_hdw_setup: video stream %p setting tolerance %u", + hdw->vid_stream,idx); + } + pvr2_stream_setup(hdw->vid_stream,hdw->usb_dev, + PVR2_VID_ENDPOINT,idx); + } + + if (!pvr2_hdw_dev_ok(hdw)) return; + + hdw->flag_init_ok = !0; + + pvr2_hdw_state_sched(hdw); +} + + +/* Set up the structure and attempt to put the device into a usable state. + This can be a time-consuming operation, which is why it is not done + internally as part of the create() step. */ +static void pvr2_hdw_setup(struct pvr2_hdw *hdw) +{ + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_setup(hdw=%p) begin",hdw); + do { + pvr2_hdw_setup_low(hdw); + pvr2_trace(PVR2_TRACE_INIT, + "pvr2_hdw_setup(hdw=%p) done, ok=%d init_ok=%d", + hdw,pvr2_hdw_dev_ok(hdw),hdw->flag_init_ok); + if (pvr2_hdw_dev_ok(hdw)) { + if (hdw->flag_init_ok) { + pvr2_trace( + PVR2_TRACE_INFO, + "Device initialization completed successfully."); + break; + } + if (hdw->fw1_state == FW1_STATE_RELOAD) { + pvr2_trace( + PVR2_TRACE_INFO, + "Device microcontroller firmware (re)loaded; it should now reset and reconnect."); + break; + } + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Device initialization was not successful."); + if (hdw->fw1_state == FW1_STATE_MISSING) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Giving up since device microcontroller firmware appears to be missing."); + break; + } + } + if (hdw->flag_modulefail) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "***WARNING*** pvrusb2 driver initialization failed due to the failure of one or more sub-device kernel modules."); + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "You need to resolve the failing condition before this driver can function. There should be some earlier messages giving more information about the problem."); + break; + } + if (procreload) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Attempting pvrusb2 recovery by reloading primary firmware."); + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "If this works, device should disconnect and reconnect in a sane state."); + hdw->fw1_state = FW1_STATE_UNKNOWN; + pvr2_upload_firmware1(hdw); + } else { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "***WARNING*** pvrusb2 device hardware appears to be jammed and I can't clear it."); + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "You might need to power cycle the pvrusb2 device in order to recover."); + } + } while (0); + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_setup(hdw=%p) end",hdw); +} + + +/* Perform second stage initialization. Set callback pointer first so that + we can avoid a possible initialization race (if the kernel thread runs + before the callback has been set). */ +int pvr2_hdw_initialize(struct pvr2_hdw *hdw, + void (*callback_func)(void *), + void *callback_data) +{ + LOCK_TAKE(hdw->big_lock); do { + if (hdw->flag_disconnected) { + /* Handle a race here: If we're already + disconnected by this point, then give up. If we + get past this then we'll remain connected for + the duration of initialization since the entire + initialization sequence is now protected by the + big_lock. */ + break; + } + hdw->state_data = callback_data; + hdw->state_func = callback_func; + pvr2_hdw_setup(hdw); + } while (0); LOCK_GIVE(hdw->big_lock); + return hdw->flag_init_ok; +} + + +/* Create, set up, and return a structure for interacting with the + underlying hardware. */ +struct pvr2_hdw *pvr2_hdw_create(struct usb_interface *intf, + const struct usb_device_id *devid) +{ + unsigned int idx,cnt1,cnt2,m; + struct pvr2_hdw *hdw = NULL; + int valid_std_mask; + struct pvr2_ctrl *cptr; + struct usb_device *usb_dev; + const struct pvr2_device_desc *hdw_desc; + __u8 ifnum; + struct v4l2_queryctrl qctrl; + struct pvr2_ctl_info *ciptr; + + usb_dev = interface_to_usbdev(intf); + + hdw_desc = (const struct pvr2_device_desc *)(devid->driver_info); + + if (hdw_desc == NULL) { + pvr2_trace(PVR2_TRACE_INIT, "pvr2_hdw_create: No device description pointer, unable to continue."); + pvr2_trace(PVR2_TRACE_INIT, + "If you have a new device type, please contact Mike Isely <isely@pobox.com> to get it included in the driver"); + goto fail; + } + + hdw = kzalloc(sizeof(*hdw),GFP_KERNEL); + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_create: hdw=%p, type \"%s\"", + hdw,hdw_desc->description); + pvr2_trace(PVR2_TRACE_INFO, "Hardware description: %s", + hdw_desc->description); + if (hdw_desc->flag_is_experimental) { + pvr2_trace(PVR2_TRACE_INFO, "**********"); + pvr2_trace(PVR2_TRACE_INFO, + "***WARNING*** Support for this device (%s) is experimental.", + hdw_desc->description); + pvr2_trace(PVR2_TRACE_INFO, + "Important functionality might not be entirely working."); + pvr2_trace(PVR2_TRACE_INFO, + "Please consider contacting the driver author to help with further stabilization of the driver."); + pvr2_trace(PVR2_TRACE_INFO, "**********"); + } + if (!hdw) goto fail; + + timer_setup(&hdw->quiescent_timer, pvr2_hdw_quiescent_timeout, 0); + + timer_setup(&hdw->decoder_stabilization_timer, + pvr2_hdw_decoder_stabilization_timeout, 0); + + timer_setup(&hdw->encoder_wait_timer, pvr2_hdw_encoder_wait_timeout, + 0); + + timer_setup(&hdw->encoder_run_timer, pvr2_hdw_encoder_run_timeout, 0); + + hdw->master_state = PVR2_STATE_DEAD; + + init_waitqueue_head(&hdw->state_wait_data); + + hdw->tuner_signal_stale = !0; + cx2341x_fill_defaults(&hdw->enc_ctl_state); + + /* Calculate which inputs are OK */ + m = 0; + if (hdw_desc->flag_has_analogtuner) m |= 1 << PVR2_CVAL_INPUT_TV; + if (hdw_desc->digital_control_scheme != PVR2_DIGITAL_SCHEME_NONE) { + m |= 1 << PVR2_CVAL_INPUT_DTV; + } + if (hdw_desc->flag_has_svideo) m |= 1 << PVR2_CVAL_INPUT_SVIDEO; + if (hdw_desc->flag_has_composite) m |= 1 << PVR2_CVAL_INPUT_COMPOSITE; + if (hdw_desc->flag_has_fmradio) m |= 1 << PVR2_CVAL_INPUT_RADIO; + hdw->input_avail_mask = m; + hdw->input_allowed_mask = hdw->input_avail_mask; + + /* If not a hybrid device, pathway_state never changes. So + initialize it here to what it should forever be. */ + if (!(hdw->input_avail_mask & (1 << PVR2_CVAL_INPUT_DTV))) { + hdw->pathway_state = PVR2_PATHWAY_ANALOG; + } else if (!(hdw->input_avail_mask & (1 << PVR2_CVAL_INPUT_TV))) { + hdw->pathway_state = PVR2_PATHWAY_DIGITAL; + } + + hdw->control_cnt = CTRLDEF_COUNT; + hdw->control_cnt += MPEGDEF_COUNT; + hdw->controls = kcalloc(hdw->control_cnt, sizeof(struct pvr2_ctrl), + GFP_KERNEL); + if (!hdw->controls) goto fail; + hdw->hdw_desc = hdw_desc; + hdw->ir_scheme_active = hdw->hdw_desc->ir_scheme; + for (idx = 0; idx < hdw->control_cnt; idx++) { + cptr = hdw->controls + idx; + cptr->hdw = hdw; + } + for (idx = 0; idx < 32; idx++) { + hdw->std_mask_ptrs[idx] = hdw->std_mask_names[idx]; + } + for (idx = 0; idx < CTRLDEF_COUNT; idx++) { + cptr = hdw->controls + idx; + cptr->info = control_defs+idx; + } + + /* Ensure that default input choice is a valid one. */ + m = hdw->input_avail_mask; + if (m) for (idx = 0; idx < (sizeof(m) << 3); idx++) { + if (!((1UL << idx) & m)) continue; + hdw->input_val = idx; + break; + } + + /* Define and configure additional controls from cx2341x module. */ + hdw->mpeg_ctrl_info = kcalloc(MPEGDEF_COUNT, + sizeof(*(hdw->mpeg_ctrl_info)), + GFP_KERNEL); + if (!hdw->mpeg_ctrl_info) goto fail; + for (idx = 0; idx < MPEGDEF_COUNT; idx++) { + cptr = hdw->controls + idx + CTRLDEF_COUNT; + ciptr = &(hdw->mpeg_ctrl_info[idx].info); + ciptr->desc = hdw->mpeg_ctrl_info[idx].desc; + ciptr->name = mpeg_ids[idx].strid; + ciptr->v4l_id = mpeg_ids[idx].id; + ciptr->skip_init = !0; + ciptr->get_value = ctrl_cx2341x_get; + ciptr->get_v4lflags = ctrl_cx2341x_getv4lflags; + ciptr->is_dirty = ctrl_cx2341x_is_dirty; + if (!idx) ciptr->clear_dirty = ctrl_cx2341x_clear_dirty; + qctrl.id = ciptr->v4l_id; + cx2341x_ctrl_query(&hdw->enc_ctl_state,&qctrl); + if (!(qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY)) { + ciptr->set_value = ctrl_cx2341x_set; + } + strscpy(hdw->mpeg_ctrl_info[idx].desc, qctrl.name, + sizeof(hdw->mpeg_ctrl_info[idx].desc)); + ciptr->default_value = qctrl.default_value; + switch (qctrl.type) { + default: + case V4L2_CTRL_TYPE_INTEGER: + ciptr->type = pvr2_ctl_int; + ciptr->def.type_int.min_value = qctrl.minimum; + ciptr->def.type_int.max_value = qctrl.maximum; + break; + case V4L2_CTRL_TYPE_BOOLEAN: + ciptr->type = pvr2_ctl_bool; + break; + case V4L2_CTRL_TYPE_MENU: + ciptr->type = pvr2_ctl_enum; + ciptr->def.type_enum.value_names = + cx2341x_ctrl_get_menu(&hdw->enc_ctl_state, + ciptr->v4l_id); + for (cnt1 = 0; + ciptr->def.type_enum.value_names[cnt1] != NULL; + cnt1++) { } + ciptr->def.type_enum.count = cnt1; + break; + } + cptr->info = ciptr; + } + + // Initialize control data regarding video standard masks + valid_std_mask = pvr2_std_get_usable(); + for (idx = 0; idx < 32; idx++) { + if (!(valid_std_mask & (1UL << idx))) continue; + cnt1 = pvr2_std_id_to_str( + hdw->std_mask_names[idx], + sizeof(hdw->std_mask_names[idx])-1, + 1UL << idx); + hdw->std_mask_names[idx][cnt1] = 0; + } + cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDAVAIL); + if (cptr) { + memcpy(&hdw->std_info_avail,cptr->info, + sizeof(hdw->std_info_avail)); + cptr->info = &hdw->std_info_avail; + hdw->std_info_avail.def.type_bitmask.bit_names = + hdw->std_mask_ptrs; + hdw->std_info_avail.def.type_bitmask.valid_bits = + valid_std_mask; + } + cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR); + if (cptr) { + memcpy(&hdw->std_info_cur,cptr->info, + sizeof(hdw->std_info_cur)); + cptr->info = &hdw->std_info_cur; + hdw->std_info_cur.def.type_bitmask.bit_names = + hdw->std_mask_ptrs; + hdw->std_info_cur.def.type_bitmask.valid_bits = + valid_std_mask; + } + cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDDETECT); + if (cptr) { + memcpy(&hdw->std_info_detect,cptr->info, + sizeof(hdw->std_info_detect)); + cptr->info = &hdw->std_info_detect; + hdw->std_info_detect.def.type_bitmask.bit_names = + hdw->std_mask_ptrs; + hdw->std_info_detect.def.type_bitmask.valid_bits = + valid_std_mask; + } + + hdw->cropcap_stale = !0; + hdw->eeprom_addr = -1; + hdw->unit_number = -1; + hdw->v4l_minor_number_video = -1; + hdw->v4l_minor_number_vbi = -1; + hdw->v4l_minor_number_radio = -1; + hdw->ctl_write_buffer = kmalloc(PVR2_CTL_BUFFSIZE,GFP_KERNEL); + if (!hdw->ctl_write_buffer) goto fail; + hdw->ctl_read_buffer = kmalloc(PVR2_CTL_BUFFSIZE,GFP_KERNEL); + if (!hdw->ctl_read_buffer) goto fail; + hdw->ctl_write_urb = usb_alloc_urb(0,GFP_KERNEL); + if (!hdw->ctl_write_urb) goto fail; + hdw->ctl_read_urb = usb_alloc_urb(0,GFP_KERNEL); + if (!hdw->ctl_read_urb) goto fail; + + if (v4l2_device_register(&intf->dev, &hdw->v4l2_dev) != 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Error registering with v4l core, giving up"); + goto fail; + } + mutex_lock(&pvr2_unit_mtx); + do { + for (idx = 0; idx < PVR_NUM; idx++) { + if (unit_pointers[idx]) continue; + hdw->unit_number = idx; + unit_pointers[idx] = hdw; + break; + } + } while (0); + mutex_unlock(&pvr2_unit_mtx); + + INIT_WORK(&hdw->workpoll, pvr2_hdw_worker_poll); + + if (hdw->unit_number == -1) + goto fail; + + cnt1 = 0; + cnt2 = scnprintf(hdw->name+cnt1,sizeof(hdw->name)-cnt1,"pvrusb2"); + cnt1 += cnt2; + if (hdw->unit_number >= 0) { + cnt2 = scnprintf(hdw->name+cnt1,sizeof(hdw->name)-cnt1,"_%c", + ('a' + hdw->unit_number)); + cnt1 += cnt2; + } + if (cnt1 >= sizeof(hdw->name)) cnt1 = sizeof(hdw->name)-1; + hdw->name[cnt1] = 0; + + pvr2_trace(PVR2_TRACE_INIT,"Driver unit number is %d, name is %s", + hdw->unit_number,hdw->name); + + hdw->tuner_type = -1; + hdw->flag_ok = !0; + + hdw->usb_intf = intf; + hdw->usb_dev = usb_dev; + + usb_make_path(hdw->usb_dev, hdw->bus_info, sizeof(hdw->bus_info)); + + ifnum = hdw->usb_intf->cur_altsetting->desc.bInterfaceNumber; + usb_set_interface(hdw->usb_dev,ifnum,0); + + mutex_init(&hdw->ctl_lock_mutex); + mutex_init(&hdw->big_lock_mutex); + + return hdw; + fail: + if (hdw) { + timer_shutdown_sync(&hdw->quiescent_timer); + timer_shutdown_sync(&hdw->decoder_stabilization_timer); + timer_shutdown_sync(&hdw->encoder_run_timer); + timer_shutdown_sync(&hdw->encoder_wait_timer); + flush_work(&hdw->workpoll); + v4l2_device_unregister(&hdw->v4l2_dev); + usb_free_urb(hdw->ctl_read_urb); + usb_free_urb(hdw->ctl_write_urb); + kfree(hdw->ctl_read_buffer); + kfree(hdw->ctl_write_buffer); + kfree(hdw->controls); + kfree(hdw->mpeg_ctrl_info); + kfree(hdw); + } + return NULL; +} + + +/* Remove _all_ associations between this driver and the underlying USB + layer. */ +static void pvr2_hdw_remove_usb_stuff(struct pvr2_hdw *hdw) +{ + if (hdw->flag_disconnected) return; + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_remove_usb_stuff: hdw=%p",hdw); + if (hdw->ctl_read_urb) { + usb_kill_urb(hdw->ctl_read_urb); + usb_free_urb(hdw->ctl_read_urb); + hdw->ctl_read_urb = NULL; + } + if (hdw->ctl_write_urb) { + usb_kill_urb(hdw->ctl_write_urb); + usb_free_urb(hdw->ctl_write_urb); + hdw->ctl_write_urb = NULL; + } + if (hdw->ctl_read_buffer) { + kfree(hdw->ctl_read_buffer); + hdw->ctl_read_buffer = NULL; + } + if (hdw->ctl_write_buffer) { + kfree(hdw->ctl_write_buffer); + hdw->ctl_write_buffer = NULL; + } + hdw->flag_disconnected = !0; + /* If we don't do this, then there will be a dangling struct device + reference to our disappearing device persisting inside the V4L + core... */ + v4l2_device_disconnect(&hdw->v4l2_dev); + hdw->usb_dev = NULL; + hdw->usb_intf = NULL; + pvr2_hdw_render_useless(hdw); +} + +void pvr2_hdw_set_v4l2_dev(struct pvr2_hdw *hdw, struct video_device *vdev) +{ + vdev->v4l2_dev = &hdw->v4l2_dev; +} + +/* Destroy hardware interaction structure */ +void pvr2_hdw_destroy(struct pvr2_hdw *hdw) +{ + if (!hdw) return; + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_destroy: hdw=%p",hdw); + flush_work(&hdw->workpoll); + timer_shutdown_sync(&hdw->quiescent_timer); + timer_shutdown_sync(&hdw->decoder_stabilization_timer); + timer_shutdown_sync(&hdw->encoder_run_timer); + timer_shutdown_sync(&hdw->encoder_wait_timer); + if (hdw->fw_buffer) { + kfree(hdw->fw_buffer); + hdw->fw_buffer = NULL; + } + if (hdw->vid_stream) { + pvr2_stream_destroy(hdw->vid_stream); + hdw->vid_stream = NULL; + } + v4l2_device_unregister(&hdw->v4l2_dev); + pvr2_hdw_disconnect(hdw); + mutex_lock(&pvr2_unit_mtx); + do { + if ((hdw->unit_number >= 0) && + (hdw->unit_number < PVR_NUM) && + (unit_pointers[hdw->unit_number] == hdw)) { + unit_pointers[hdw->unit_number] = NULL; + } + } while (0); + mutex_unlock(&pvr2_unit_mtx); + kfree(hdw->controls); + kfree(hdw->mpeg_ctrl_info); + kfree(hdw); +} + + +int pvr2_hdw_dev_ok(struct pvr2_hdw *hdw) +{ + return (hdw && hdw->flag_ok); +} + + +/* Called when hardware has been unplugged */ +void pvr2_hdw_disconnect(struct pvr2_hdw *hdw) +{ + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_disconnect(hdw=%p)",hdw); + LOCK_TAKE(hdw->big_lock); + pvr2_i2c_core_done(hdw); + LOCK_TAKE(hdw->ctl_lock); + pvr2_hdw_remove_usb_stuff(hdw); + LOCK_GIVE(hdw->ctl_lock); + LOCK_GIVE(hdw->big_lock); +} + + +/* Get the number of defined controls */ +unsigned int pvr2_hdw_get_ctrl_count(struct pvr2_hdw *hdw) +{ + return hdw->control_cnt; +} + + +/* Retrieve a control handle given its index (0..count-1) */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_index(struct pvr2_hdw *hdw, + unsigned int idx) +{ + if (idx >= hdw->control_cnt) return NULL; + return hdw->controls + idx; +} + + +/* Retrieve a control handle given its index (0..count-1) */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_id(struct pvr2_hdw *hdw, + unsigned int ctl_id) +{ + struct pvr2_ctrl *cptr; + unsigned int idx; + int i; + + /* This could be made a lot more efficient, but for now... */ + for (idx = 0; idx < hdw->control_cnt; idx++) { + cptr = hdw->controls + idx; + i = cptr->info->internal_id; + if (i && (i == ctl_id)) return cptr; + } + return NULL; +} + + +/* Given a V4L ID, retrieve the control structure associated with it. */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_v4l(struct pvr2_hdw *hdw,unsigned int ctl_id) +{ + struct pvr2_ctrl *cptr; + unsigned int idx; + int i; + + /* This could be made a lot more efficient, but for now... */ + for (idx = 0; idx < hdw->control_cnt; idx++) { + cptr = hdw->controls + idx; + i = cptr->info->v4l_id; + if (i && (i == ctl_id)) return cptr; + } + return NULL; +} + + +/* Given a V4L ID for its immediate predecessor, retrieve the control + structure associated with it. */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_nextv4l(struct pvr2_hdw *hdw, + unsigned int ctl_id) +{ + struct pvr2_ctrl *cptr,*cp2; + unsigned int idx; + int i; + + /* This could be made a lot more efficient, but for now... */ + cp2 = NULL; + for (idx = 0; idx < hdw->control_cnt; idx++) { + cptr = hdw->controls + idx; + i = cptr->info->v4l_id; + if (!i) continue; + if (i <= ctl_id) continue; + if (cp2 && (cp2->info->v4l_id < i)) continue; + cp2 = cptr; + } + return cp2; + return NULL; +} + + +static const char *get_ctrl_typename(enum pvr2_ctl_type tp) +{ + switch (tp) { + case pvr2_ctl_int: return "integer"; + case pvr2_ctl_enum: return "enum"; + case pvr2_ctl_bool: return "boolean"; + case pvr2_ctl_bitmask: return "bitmask"; + } + return ""; +} + + +static void pvr2_subdev_set_control(struct pvr2_hdw *hdw, int id, + const char *name, int val) +{ + struct v4l2_control ctrl; + struct v4l2_subdev *sd; + + pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 %s=%d", name, val); + memset(&ctrl, 0, sizeof(ctrl)); + ctrl.id = id; + ctrl.value = val; + + v4l2_device_for_each_subdev(sd, &hdw->v4l2_dev) + v4l2_s_ctrl(NULL, sd->ctrl_handler, &ctrl); +} + +#define PVR2_SUBDEV_SET_CONTROL(hdw, id, lab) \ + if ((hdw)->lab##_dirty || (hdw)->force_dirty) { \ + pvr2_subdev_set_control(hdw, id, #lab, (hdw)->lab##_val); \ + } + +static v4l2_std_id pvr2_hdw_get_detected_std(struct pvr2_hdw *hdw) +{ + v4l2_std_id std; + std = (v4l2_std_id)hdw->std_mask_avail; + v4l2_device_call_all(&hdw->v4l2_dev, 0, + video, querystd, &std); + return std; +} + +/* Execute whatever commands are required to update the state of all the + sub-devices so that they match our current control values. */ +static void pvr2_subdev_update(struct pvr2_hdw *hdw) +{ + struct v4l2_subdev *sd; + unsigned int id; + pvr2_subdev_update_func fp; + + pvr2_trace(PVR2_TRACE_CHIPS, "subdev update..."); + + if (hdw->tuner_updated || hdw->force_dirty) { + struct tuner_setup setup; + pvr2_trace(PVR2_TRACE_CHIPS, "subdev tuner set_type(%d)", + hdw->tuner_type); + if (((int)(hdw->tuner_type)) >= 0) { + memset(&setup, 0, sizeof(setup)); + setup.addr = ADDR_UNSET; + setup.type = hdw->tuner_type; + setup.mode_mask = T_RADIO | T_ANALOG_TV; + v4l2_device_call_all(&hdw->v4l2_dev, 0, + tuner, s_type_addr, &setup); + } + } + + if (hdw->input_dirty || hdw->std_dirty || hdw->force_dirty) { + pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_standard"); + if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) { + v4l2_device_call_all(&hdw->v4l2_dev, 0, + tuner, s_radio); + } else { + v4l2_std_id vs; + vs = hdw->std_mask_cur; + v4l2_device_call_all(&hdw->v4l2_dev, 0, + video, s_std, vs); + pvr2_hdw_cx25840_vbi_hack(hdw); + } + hdw->tuner_signal_stale = !0; + hdw->cropcap_stale = !0; + } + + PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_BRIGHTNESS, brightness); + PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_CONTRAST, contrast); + PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_SATURATION, saturation); + PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_HUE, hue); + PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_MUTE, mute); + PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_VOLUME, volume); + PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_BALANCE, balance); + PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_BASS, bass); + PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_TREBLE, treble); + + if (hdw->input_dirty || hdw->audiomode_dirty || hdw->force_dirty) { + struct v4l2_tuner vt; + memset(&vt, 0, sizeof(vt)); + vt.type = (hdw->input_val == PVR2_CVAL_INPUT_RADIO) ? + V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + vt.audmode = hdw->audiomode_val; + v4l2_device_call_all(&hdw->v4l2_dev, 0, tuner, s_tuner, &vt); + } + + if (hdw->freqDirty || hdw->force_dirty) { + unsigned long fv; + struct v4l2_frequency freq; + fv = pvr2_hdw_get_cur_freq(hdw); + pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_freq(%lu)", fv); + if (hdw->tuner_signal_stale) pvr2_hdw_status_poll(hdw); + memset(&freq, 0, sizeof(freq)); + if (hdw->tuner_signal_info.capability & V4L2_TUNER_CAP_LOW) { + /* ((fv * 1000) / 62500) */ + freq.frequency = (fv * 2) / 125; + } else { + freq.frequency = fv / 62500; + } + /* tuner-core currently doesn't seem to care about this, but + let's set it anyway for completeness. */ + if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) { + freq.type = V4L2_TUNER_RADIO; + } else { + freq.type = V4L2_TUNER_ANALOG_TV; + } + freq.tuner = 0; + v4l2_device_call_all(&hdw->v4l2_dev, 0, tuner, + s_frequency, &freq); + } + + if (hdw->res_hor_dirty || hdw->res_ver_dirty || hdw->force_dirty) { + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + format.format.width = hdw->res_hor_val; + format.format.height = hdw->res_ver_val; + format.format.code = MEDIA_BUS_FMT_FIXED; + pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_size(%dx%d)", + format.format.width, format.format.height); + v4l2_device_call_all(&hdw->v4l2_dev, 0, pad, set_fmt, + NULL, &format); + } + + if (hdw->srate_dirty || hdw->force_dirty) { + u32 val; + pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_audio %d", + hdw->srate_val); + switch (hdw->srate_val) { + default: + case V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000: + val = 48000; + break; + case V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100: + val = 44100; + break; + case V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000: + val = 32000; + break; + } + v4l2_device_call_all(&hdw->v4l2_dev, 0, + audio, s_clock_freq, val); + } + + /* Unable to set crop parameters; there is apparently no equivalent + for VIDIOC_S_CROP */ + + v4l2_device_for_each_subdev(sd, &hdw->v4l2_dev) { + id = sd->grp_id; + if (id >= ARRAY_SIZE(pvr2_module_update_functions)) continue; + fp = pvr2_module_update_functions[id]; + if (!fp) continue; + (*fp)(hdw, sd); + } + + if (hdw->tuner_signal_stale || hdw->cropcap_stale) { + pvr2_hdw_status_poll(hdw); + } +} + + +/* Figure out if we need to commit control changes. If so, mark internal + state flags to indicate this fact and return true. Otherwise do nothing + else and return false. */ +static int pvr2_hdw_commit_setup(struct pvr2_hdw *hdw) +{ + unsigned int idx; + struct pvr2_ctrl *cptr; + int value; + int commit_flag = hdw->force_dirty; + char buf[100]; + unsigned int bcnt,ccnt; + + for (idx = 0; idx < hdw->control_cnt; idx++) { + cptr = hdw->controls + idx; + if (!cptr->info->is_dirty) continue; + if (!cptr->info->is_dirty(cptr)) continue; + commit_flag = !0; + + if (!(pvrusb2_debug & PVR2_TRACE_CTL)) continue; + bcnt = scnprintf(buf,sizeof(buf),"\"%s\" <-- ", + cptr->info->name); + value = 0; + cptr->info->get_value(cptr,&value); + pvr2_ctrl_value_to_sym_internal(cptr,~0,value, + buf+bcnt, + sizeof(buf)-bcnt,&ccnt); + bcnt += ccnt; + bcnt += scnprintf(buf+bcnt,sizeof(buf)-bcnt," <%s>", + get_ctrl_typename(cptr->info->type)); + pvr2_trace(PVR2_TRACE_CTL, + "/*--TRACE_COMMIT--*/ %.*s", + bcnt,buf); + } + + if (!commit_flag) { + /* Nothing has changed */ + return 0; + } + + hdw->state_pipeline_config = 0; + trace_stbit("state_pipeline_config",hdw->state_pipeline_config); + pvr2_hdw_state_sched(hdw); + + return !0; +} + + +/* Perform all operations needed to commit all control changes. This must + be performed in synchronization with the pipeline state and is thus + expected to be called as part of the driver's worker thread. Return + true if commit successful, otherwise return false to indicate that + commit isn't possible at this time. */ +static int pvr2_hdw_commit_execute(struct pvr2_hdw *hdw) +{ + unsigned int idx; + struct pvr2_ctrl *cptr; + int disruptive_change; + + if (hdw->input_dirty && hdw->state_pathway_ok && + (((hdw->input_val == PVR2_CVAL_INPUT_DTV) ? + PVR2_PATHWAY_DIGITAL : PVR2_PATHWAY_ANALOG) != + hdw->pathway_state)) { + /* Change of mode being asked for... */ + hdw->state_pathway_ok = 0; + trace_stbit("state_pathway_ok", hdw->state_pathway_ok); + } + if (!hdw->state_pathway_ok) { + /* Can't commit anything until pathway is ok. */ + return 0; + } + + /* Handle some required side effects when the video standard is + changed.... */ + if (hdw->std_dirty) { + int nvres; + int gop_size; + if (hdw->std_mask_cur & V4L2_STD_525_60) { + nvres = 480; + gop_size = 15; + } else { + nvres = 576; + gop_size = 12; + } + /* Rewrite the vertical resolution to be appropriate to the + video standard that has been selected. */ + if (nvres != hdw->res_ver_val) { + hdw->res_ver_val = nvres; + hdw->res_ver_dirty = !0; + } + /* Rewrite the GOP size to be appropriate to the video + standard that has been selected. */ + if (gop_size != hdw->enc_ctl_state.video_gop_size) { + struct v4l2_ext_controls cs; + struct v4l2_ext_control c1; + memset(&cs, 0, sizeof(cs)); + memset(&c1, 0, sizeof(c1)); + cs.controls = &c1; + cs.count = 1; + c1.id = V4L2_CID_MPEG_VIDEO_GOP_SIZE; + c1.value = gop_size; + cx2341x_ext_ctrls(&hdw->enc_ctl_state, 0, &cs, + VIDIOC_S_EXT_CTRLS); + } + } + + /* The broadcast decoder can only scale down, so if + * res_*_dirty && crop window < output format ==> enlarge crop. + * + * The mpeg encoder receives fields of res_hor_val dots and + * res_ver_val halflines. Limits: hor<=720, ver<=576. + */ + if (hdw->res_hor_dirty && hdw->cropw_val < hdw->res_hor_val) { + hdw->cropw_val = hdw->res_hor_val; + hdw->cropw_dirty = !0; + } else if (hdw->cropw_dirty) { + hdw->res_hor_dirty = !0; /* must rescale */ + hdw->res_hor_val = min(720, hdw->cropw_val); + } + if (hdw->res_ver_dirty && hdw->croph_val < hdw->res_ver_val) { + hdw->croph_val = hdw->res_ver_val; + hdw->croph_dirty = !0; + } else if (hdw->croph_dirty) { + int nvres = hdw->std_mask_cur & V4L2_STD_525_60 ? 480 : 576; + hdw->res_ver_dirty = !0; + hdw->res_ver_val = min(nvres, hdw->croph_val); + } + + /* If any of the below has changed, then we can't do the update + while the pipeline is running. Pipeline must be paused first + and decoder -> encoder connection be made quiescent before we + can proceed. */ + disruptive_change = + (hdw->std_dirty || + hdw->enc_unsafe_stale || + hdw->srate_dirty || + hdw->res_ver_dirty || + hdw->res_hor_dirty || + hdw->cropw_dirty || + hdw->croph_dirty || + hdw->input_dirty || + (hdw->active_stream_type != hdw->desired_stream_type)); + if (disruptive_change && !hdw->state_pipeline_idle) { + /* Pipeline is not idle; we can't proceed. Arrange to + cause pipeline to stop so that we can try this again + later.... */ + hdw->state_pipeline_pause = !0; + return 0; + } + + if (hdw->srate_dirty) { + /* Write new sample rate into control structure since + * the master copy is stale. We must track srate + * separate from the mpeg control structure because + * other logic also uses this value. */ + struct v4l2_ext_controls cs; + struct v4l2_ext_control c1; + memset(&cs,0,sizeof(cs)); + memset(&c1,0,sizeof(c1)); + cs.controls = &c1; + cs.count = 1; + c1.id = V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ; + c1.value = hdw->srate_val; + cx2341x_ext_ctrls(&hdw->enc_ctl_state, 0, &cs,VIDIOC_S_EXT_CTRLS); + } + + if (hdw->active_stream_type != hdw->desired_stream_type) { + /* Handle any side effects of stream config here */ + hdw->active_stream_type = hdw->desired_stream_type; + } + + if (hdw->hdw_desc->signal_routing_scheme == + PVR2_ROUTING_SCHEME_GOTVIEW) { + u32 b; + /* Handle GOTVIEW audio switching */ + pvr2_hdw_gpio_get_out(hdw,&b); + if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) { + /* Set GPIO 11 */ + pvr2_hdw_gpio_chg_out(hdw,(1 << 11),~0); + } else { + /* Clear GPIO 11 */ + pvr2_hdw_gpio_chg_out(hdw,(1 << 11),0); + } + } + + /* Check and update state for all sub-devices. */ + pvr2_subdev_update(hdw); + + hdw->tuner_updated = 0; + hdw->force_dirty = 0; + for (idx = 0; idx < hdw->control_cnt; idx++) { + cptr = hdw->controls + idx; + if (!cptr->info->clear_dirty) continue; + cptr->info->clear_dirty(cptr); + } + + if ((hdw->pathway_state == PVR2_PATHWAY_ANALOG) && + hdw->state_encoder_run) { + /* If encoder isn't running or it can't be touched, then + this will get worked out later when we start the + encoder. */ + if (pvr2_encoder_adjust(hdw) < 0) return !0; + } + + hdw->state_pipeline_config = !0; + /* Hardware state may have changed in a way to cause the cropping + capabilities to have changed. So mark it stale, which will + cause a later re-fetch. */ + trace_stbit("state_pipeline_config",hdw->state_pipeline_config); + return !0; +} + + +int pvr2_hdw_commit_ctl(struct pvr2_hdw *hdw) +{ + int fl; + LOCK_TAKE(hdw->big_lock); + fl = pvr2_hdw_commit_setup(hdw); + LOCK_GIVE(hdw->big_lock); + if (!fl) return 0; + return pvr2_hdw_wait(hdw,0); +} + + +static void pvr2_hdw_worker_poll(struct work_struct *work) +{ + int fl = 0; + struct pvr2_hdw *hdw = container_of(work,struct pvr2_hdw,workpoll); + LOCK_TAKE(hdw->big_lock); do { + fl = pvr2_hdw_state_eval(hdw); + } while (0); LOCK_GIVE(hdw->big_lock); + if (fl && hdw->state_func) { + hdw->state_func(hdw->state_data); + } +} + + +static int pvr2_hdw_wait(struct pvr2_hdw *hdw,int state) +{ + return wait_event_interruptible( + hdw->state_wait_data, + (hdw->state_stale == 0) && + (!state || (hdw->master_state != state))); +} + + +/* Return name for this driver instance */ +const char *pvr2_hdw_get_driver_name(struct pvr2_hdw *hdw) +{ + return hdw->name; +} + + +const char *pvr2_hdw_get_desc(struct pvr2_hdw *hdw) +{ + return hdw->hdw_desc->description; +} + + +const char *pvr2_hdw_get_type(struct pvr2_hdw *hdw) +{ + return hdw->hdw_desc->shortname; +} + + +int pvr2_hdw_is_hsm(struct pvr2_hdw *hdw) +{ + int result; + LOCK_TAKE(hdw->ctl_lock); do { + hdw->cmd_buffer[0] = FX2CMD_GET_USB_SPEED; + result = pvr2_send_request(hdw, + hdw->cmd_buffer,1, + hdw->cmd_buffer,1); + if (result < 0) break; + result = (hdw->cmd_buffer[0] != 0); + } while(0); LOCK_GIVE(hdw->ctl_lock); + return result; +} + + +/* Execute poll of tuner status */ +void pvr2_hdw_execute_tuner_poll(struct pvr2_hdw *hdw) +{ + LOCK_TAKE(hdw->big_lock); do { + pvr2_hdw_status_poll(hdw); + } while (0); LOCK_GIVE(hdw->big_lock); +} + + +static int pvr2_hdw_check_cropcap(struct pvr2_hdw *hdw) +{ + if (!hdw->cropcap_stale) { + return 0; + } + pvr2_hdw_status_poll(hdw); + if (hdw->cropcap_stale) { + return -EIO; + } + return 0; +} + + +/* Return information about cropping capabilities */ +int pvr2_hdw_get_cropcap(struct pvr2_hdw *hdw, struct v4l2_cropcap *pp) +{ + int stat = 0; + LOCK_TAKE(hdw->big_lock); + stat = pvr2_hdw_check_cropcap(hdw); + if (!stat) { + memcpy(pp, &hdw->cropcap_info, sizeof(hdw->cropcap_info)); + } + LOCK_GIVE(hdw->big_lock); + return stat; +} + + +/* Return information about the tuner */ +int pvr2_hdw_get_tuner_status(struct pvr2_hdw *hdw,struct v4l2_tuner *vtp) +{ + LOCK_TAKE(hdw->big_lock); + do { + if (hdw->tuner_signal_stale) { + pvr2_hdw_status_poll(hdw); + } + memcpy(vtp,&hdw->tuner_signal_info,sizeof(struct v4l2_tuner)); + } while (0); + LOCK_GIVE(hdw->big_lock); + return 0; +} + + +/* Get handle to video output stream */ +struct pvr2_stream *pvr2_hdw_get_video_stream(struct pvr2_hdw *hp) +{ + return hp->vid_stream; +} + + +void pvr2_hdw_trigger_module_log(struct pvr2_hdw *hdw) +{ + int nr = pvr2_hdw_get_unit_number(hdw); + LOCK_TAKE(hdw->big_lock); + do { + pr_info("pvrusb2: ================= START STATUS CARD #%d =================\n", nr); + v4l2_device_call_all(&hdw->v4l2_dev, 0, core, log_status); + pvr2_trace(PVR2_TRACE_INFO,"cx2341x config:"); + cx2341x_log_status(&hdw->enc_ctl_state, "pvrusb2"); + pvr2_hdw_state_log_state(hdw); + pr_info("pvrusb2: ================== END STATUS CARD #%d ==================\n", nr); + } while (0); + LOCK_GIVE(hdw->big_lock); +} + + +/* Grab EEPROM contents, needed for direct method. */ +#define EEPROM_SIZE 8192 +#define trace_eeprom(...) pvr2_trace(PVR2_TRACE_EEPROM,__VA_ARGS__) +static u8 *pvr2_full_eeprom_fetch(struct pvr2_hdw *hdw) +{ + struct i2c_msg msg[2]; + u8 *eeprom; + u8 iadd[2]; + u8 addr; + u16 eepromSize; + unsigned int offs; + int ret; + int mode16 = 0; + unsigned pcnt,tcnt; + eeprom = kzalloc(EEPROM_SIZE, GFP_KERNEL); + if (!eeprom) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to allocate memory required to read eeprom"); + return NULL; + } + + trace_eeprom("Value for eeprom addr from controller was 0x%x", + hdw->eeprom_addr); + addr = hdw->eeprom_addr; + /* Seems that if the high bit is set, then the *real* eeprom + address is shifted right now bit position (noticed this in + newer PVR USB2 hardware) */ + if (addr & 0x80) addr >>= 1; + + /* FX2 documentation states that a 16bit-addressed eeprom is + expected if the I2C address is an odd number (yeah, this is + strange but it's what they do) */ + mode16 = (addr & 1); + eepromSize = (mode16 ? EEPROM_SIZE : 256); + trace_eeprom("Examining %d byte eeprom at location 0x%x using %d bit addressing", + eepromSize, addr, + mode16 ? 16 : 8); + + msg[0].addr = addr; + msg[0].flags = 0; + msg[0].len = mode16 ? 2 : 1; + msg[0].buf = iadd; + msg[1].addr = addr; + msg[1].flags = I2C_M_RD; + + /* We have to do the actual eeprom data fetch ourselves, because + (1) we're only fetching part of the eeprom, and (2) if we were + getting the whole thing our I2C driver can't grab it in one + pass - which is what tveeprom is otherwise going to attempt */ + for (tcnt = 0; tcnt < EEPROM_SIZE; tcnt += pcnt) { + pcnt = 16; + if (pcnt + tcnt > EEPROM_SIZE) pcnt = EEPROM_SIZE-tcnt; + offs = tcnt + (eepromSize - EEPROM_SIZE); + if (mode16) { + iadd[0] = offs >> 8; + iadd[1] = offs; + } else { + iadd[0] = offs; + } + msg[1].len = pcnt; + msg[1].buf = eeprom+tcnt; + if ((ret = i2c_transfer(&hdw->i2c_adap, + msg,ARRAY_SIZE(msg))) != 2) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "eeprom fetch set offs err=%d",ret); + kfree(eeprom); + return NULL; + } + } + return eeprom; +} + + +void pvr2_hdw_cpufw_set_enabled(struct pvr2_hdw *hdw, + int mode, + int enable_flag) +{ + int ret; + u16 address; + unsigned int pipe; + LOCK_TAKE(hdw->big_lock); + do { + if ((hdw->fw_buffer == NULL) == !enable_flag) break; + + if (!enable_flag) { + pvr2_trace(PVR2_TRACE_FIRMWARE, + "Cleaning up after CPU firmware fetch"); + kfree(hdw->fw_buffer); + hdw->fw_buffer = NULL; + hdw->fw_size = 0; + if (hdw->fw_cpu_flag) { + /* Now release the CPU. It will disconnect + and reconnect later. */ + pvr2_hdw_cpureset_assert(hdw,0); + } + break; + } + + hdw->fw_cpu_flag = (mode != 2); + if (hdw->fw_cpu_flag) { + hdw->fw_size = (mode == 1) ? 0x4000 : 0x2000; + pvr2_trace(PVR2_TRACE_FIRMWARE, + "Preparing to suck out CPU firmware (size=%u)", + hdw->fw_size); + hdw->fw_buffer = kzalloc(hdw->fw_size,GFP_KERNEL); + if (!hdw->fw_buffer) { + hdw->fw_size = 0; + break; + } + + /* We have to hold the CPU during firmware upload. */ + pvr2_hdw_cpureset_assert(hdw,1); + + /* download the firmware from address 0000-1fff in 2048 + (=0x800) bytes chunk. */ + + pvr2_trace(PVR2_TRACE_FIRMWARE, + "Grabbing CPU firmware"); + pipe = usb_rcvctrlpipe(hdw->usb_dev, 0); + for(address = 0; address < hdw->fw_size; + address += 0x800) { + ret = usb_control_msg(hdw->usb_dev,pipe, + 0xa0,0xc0, + address,0, + hdw->fw_buffer+address, + 0x800,1000); + if (ret < 0) break; + } + + pvr2_trace(PVR2_TRACE_FIRMWARE, + "Done grabbing CPU firmware"); + } else { + pvr2_trace(PVR2_TRACE_FIRMWARE, + "Sucking down EEPROM contents"); + hdw->fw_buffer = pvr2_full_eeprom_fetch(hdw); + if (!hdw->fw_buffer) { + pvr2_trace(PVR2_TRACE_FIRMWARE, + "EEPROM content suck failed."); + break; + } + hdw->fw_size = EEPROM_SIZE; + pvr2_trace(PVR2_TRACE_FIRMWARE, + "Done sucking down EEPROM contents"); + } + } while (0); + LOCK_GIVE(hdw->big_lock); +} + + +/* Return true if we're in a mode for retrieval CPU firmware */ +int pvr2_hdw_cpufw_get_enabled(struct pvr2_hdw *hdw) +{ + return hdw->fw_buffer != NULL; +} + + +int pvr2_hdw_cpufw_get(struct pvr2_hdw *hdw,unsigned int offs, + char *buf,unsigned int cnt) +{ + int ret = -EINVAL; + LOCK_TAKE(hdw->big_lock); + do { + if (!buf) break; + if (!cnt) break; + + if (!hdw->fw_buffer) { + ret = -EIO; + break; + } + + if (offs >= hdw->fw_size) { + pvr2_trace(PVR2_TRACE_FIRMWARE, + "Read firmware data offs=%d EOF", + offs); + ret = 0; + break; + } + + if (offs + cnt > hdw->fw_size) cnt = hdw->fw_size - offs; + + memcpy(buf,hdw->fw_buffer+offs,cnt); + + pvr2_trace(PVR2_TRACE_FIRMWARE, + "Read firmware data offs=%d cnt=%d", + offs,cnt); + ret = cnt; + } while (0); + LOCK_GIVE(hdw->big_lock); + + return ret; +} + + +int pvr2_hdw_v4l_get_minor_number(struct pvr2_hdw *hdw, + enum pvr2_v4l_type index) +{ + switch (index) { + case pvr2_v4l_type_video: return hdw->v4l_minor_number_video; + case pvr2_v4l_type_vbi: return hdw->v4l_minor_number_vbi; + case pvr2_v4l_type_radio: return hdw->v4l_minor_number_radio; + default: return -1; + } +} + + +/* Store a v4l minor device number */ +void pvr2_hdw_v4l_store_minor_number(struct pvr2_hdw *hdw, + enum pvr2_v4l_type index,int v) +{ + switch (index) { + case pvr2_v4l_type_video: hdw->v4l_minor_number_video = v;break; + case pvr2_v4l_type_vbi: hdw->v4l_minor_number_vbi = v;break; + case pvr2_v4l_type_radio: hdw->v4l_minor_number_radio = v;break; + default: break; + } +} + + +static void pvr2_ctl_write_complete(struct urb *urb) +{ + struct pvr2_hdw *hdw = urb->context; + hdw->ctl_write_pend_flag = 0; + if (hdw->ctl_read_pend_flag) return; + complete(&hdw->ctl_done); +} + + +static void pvr2_ctl_read_complete(struct urb *urb) +{ + struct pvr2_hdw *hdw = urb->context; + hdw->ctl_read_pend_flag = 0; + if (hdw->ctl_write_pend_flag) return; + complete(&hdw->ctl_done); +} + +struct hdw_timer { + struct timer_list timer; + struct pvr2_hdw *hdw; +}; + +static void pvr2_ctl_timeout(struct timer_list *t) +{ + struct hdw_timer *timer = from_timer(timer, t, timer); + struct pvr2_hdw *hdw = timer->hdw; + + if (hdw->ctl_write_pend_flag || hdw->ctl_read_pend_flag) { + hdw->ctl_timeout_flag = !0; + if (hdw->ctl_write_pend_flag) + usb_unlink_urb(hdw->ctl_write_urb); + if (hdw->ctl_read_pend_flag) + usb_unlink_urb(hdw->ctl_read_urb); + } +} + + +/* Issue a command and get a response from the device. This extended + version includes a probe flag (which if set means that device errors + should not be logged or treated as fatal) and a timeout in jiffies. + This can be used to non-lethally probe the health of endpoint 1. */ +static int pvr2_send_request_ex(struct pvr2_hdw *hdw, + unsigned int timeout,int probe_fl, + void *write_data,unsigned int write_len, + void *read_data,unsigned int read_len) +{ + unsigned int idx; + int status = 0; + struct hdw_timer timer = { + .hdw = hdw, + }; + + if (!hdw->ctl_lock_held) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Attempted to execute control transfer without lock!!"); + return -EDEADLK; + } + if (!hdw->flag_ok && !probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Attempted to execute control transfer when device not ok"); + return -EIO; + } + if (!(hdw->ctl_read_urb && hdw->ctl_write_urb)) { + if (!probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Attempted to execute control transfer when USB is disconnected"); + } + return -ENOTTY; + } + + /* Ensure that we have sane parameters */ + if (!write_data) write_len = 0; + if (!read_data) read_len = 0; + if (write_len > PVR2_CTL_BUFFSIZE) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Attempted to execute %d byte control-write transfer (limit=%d)", + write_len,PVR2_CTL_BUFFSIZE); + return -EINVAL; + } + if (read_len > PVR2_CTL_BUFFSIZE) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Attempted to execute %d byte control-read transfer (limit=%d)", + write_len,PVR2_CTL_BUFFSIZE); + return -EINVAL; + } + if ((!write_len) && (!read_len)) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Attempted to execute null control transfer?"); + return -EINVAL; + } + + + hdw->cmd_debug_state = 1; + if (write_len && write_data) + hdw->cmd_debug_code = ((unsigned char *)write_data)[0]; + else + hdw->cmd_debug_code = 0; + hdw->cmd_debug_write_len = write_len; + hdw->cmd_debug_read_len = read_len; + + /* Initialize common stuff */ + init_completion(&hdw->ctl_done); + hdw->ctl_timeout_flag = 0; + hdw->ctl_write_pend_flag = 0; + hdw->ctl_read_pend_flag = 0; + timer_setup_on_stack(&timer.timer, pvr2_ctl_timeout, 0); + timer.timer.expires = jiffies + timeout; + + if (write_len && write_data) { + hdw->cmd_debug_state = 2; + /* Transfer write data to internal buffer */ + for (idx = 0; idx < write_len; idx++) { + hdw->ctl_write_buffer[idx] = + ((unsigned char *)write_data)[idx]; + } + /* Initiate a write request */ + usb_fill_bulk_urb(hdw->ctl_write_urb, + hdw->usb_dev, + usb_sndbulkpipe(hdw->usb_dev, + PVR2_CTL_WRITE_ENDPOINT), + hdw->ctl_write_buffer, + write_len, + pvr2_ctl_write_complete, + hdw); + hdw->ctl_write_urb->actual_length = 0; + hdw->ctl_write_pend_flag = !0; + if (usb_urb_ep_type_check(hdw->ctl_write_urb)) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Invalid write control endpoint"); + return -EINVAL; + } + status = usb_submit_urb(hdw->ctl_write_urb,GFP_KERNEL); + if (status < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to submit write-control URB status=%d", +status); + hdw->ctl_write_pend_flag = 0; + goto done; + } + } + + if (read_len) { + hdw->cmd_debug_state = 3; + memset(hdw->ctl_read_buffer,0x43,read_len); + /* Initiate a read request */ + usb_fill_bulk_urb(hdw->ctl_read_urb, + hdw->usb_dev, + usb_rcvbulkpipe(hdw->usb_dev, + PVR2_CTL_READ_ENDPOINT), + hdw->ctl_read_buffer, + read_len, + pvr2_ctl_read_complete, + hdw); + hdw->ctl_read_urb->actual_length = 0; + hdw->ctl_read_pend_flag = !0; + if (usb_urb_ep_type_check(hdw->ctl_read_urb)) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Invalid read control endpoint"); + return -EINVAL; + } + status = usb_submit_urb(hdw->ctl_read_urb,GFP_KERNEL); + if (status < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to submit read-control URB status=%d", +status); + hdw->ctl_read_pend_flag = 0; + goto done; + } + } + + /* Start timer */ + add_timer(&timer.timer); + + /* Now wait for all I/O to complete */ + hdw->cmd_debug_state = 4; + while (hdw->ctl_write_pend_flag || hdw->ctl_read_pend_flag) { + wait_for_completion(&hdw->ctl_done); + } + hdw->cmd_debug_state = 5; + + /* Stop timer */ + del_timer_sync(&timer.timer); + + hdw->cmd_debug_state = 6; + status = 0; + + if (hdw->ctl_timeout_flag) { + status = -ETIMEDOUT; + if (!probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Timed out control-write"); + } + goto done; + } + + if (write_len) { + /* Validate results of write request */ + if ((hdw->ctl_write_urb->status != 0) && + (hdw->ctl_write_urb->status != -ENOENT) && + (hdw->ctl_write_urb->status != -ESHUTDOWN) && + (hdw->ctl_write_urb->status != -ECONNRESET)) { + /* USB subsystem is reporting some kind of failure + on the write */ + status = hdw->ctl_write_urb->status; + if (!probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "control-write URB failure, status=%d", + status); + } + goto done; + } + if (hdw->ctl_write_urb->actual_length < write_len) { + /* Failed to write enough data */ + status = -EIO; + if (!probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "control-write URB short, expected=%d got=%d", + write_len, + hdw->ctl_write_urb->actual_length); + } + goto done; + } + } + if (read_len && read_data) { + /* Validate results of read request */ + if ((hdw->ctl_read_urb->status != 0) && + (hdw->ctl_read_urb->status != -ENOENT) && + (hdw->ctl_read_urb->status != -ESHUTDOWN) && + (hdw->ctl_read_urb->status != -ECONNRESET)) { + /* USB subsystem is reporting some kind of failure + on the read */ + status = hdw->ctl_read_urb->status; + if (!probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "control-read URB failure, status=%d", + status); + } + goto done; + } + if (hdw->ctl_read_urb->actual_length < read_len) { + /* Failed to read enough data */ + status = -EIO; + if (!probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "control-read URB short, expected=%d got=%d", + read_len, + hdw->ctl_read_urb->actual_length); + } + goto done; + } + /* Transfer retrieved data out from internal buffer */ + for (idx = 0; idx < read_len; idx++) { + ((unsigned char *)read_data)[idx] = + hdw->ctl_read_buffer[idx]; + } + } + + done: + + hdw->cmd_debug_state = 0; + if ((status < 0) && (!probe_fl)) { + pvr2_hdw_render_useless(hdw); + } + destroy_timer_on_stack(&timer.timer); + + return status; +} + + +int pvr2_send_request(struct pvr2_hdw *hdw, + void *write_data,unsigned int write_len, + void *read_data,unsigned int read_len) +{ + return pvr2_send_request_ex(hdw,HZ*4,0, + write_data,write_len, + read_data,read_len); +} + + +static int pvr2_issue_simple_cmd(struct pvr2_hdw *hdw,u32 cmdcode) +{ + int ret; + unsigned int cnt = 1; + unsigned int args = 0; + LOCK_TAKE(hdw->ctl_lock); + hdw->cmd_buffer[0] = cmdcode & 0xffu; + args = (cmdcode >> 8) & 0xffu; + args = (args > 2) ? 2 : args; + if (args) { + cnt += args; + hdw->cmd_buffer[1] = (cmdcode >> 16) & 0xffu; + if (args > 1) { + hdw->cmd_buffer[2] = (cmdcode >> 24) & 0xffu; + } + } + if (pvrusb2_debug & PVR2_TRACE_INIT) { + unsigned int idx; + unsigned int ccnt,bcnt; + char tbuf[50]; + cmdcode &= 0xffu; + bcnt = 0; + ccnt = scnprintf(tbuf+bcnt, + sizeof(tbuf)-bcnt, + "Sending FX2 command 0x%x",cmdcode); + bcnt += ccnt; + for (idx = 0; idx < ARRAY_SIZE(pvr2_fx2cmd_desc); idx++) { + if (pvr2_fx2cmd_desc[idx].id == cmdcode) { + ccnt = scnprintf(tbuf+bcnt, + sizeof(tbuf)-bcnt, + " \"%s\"", + pvr2_fx2cmd_desc[idx].desc); + bcnt += ccnt; + break; + } + } + if (args) { + ccnt = scnprintf(tbuf+bcnt, + sizeof(tbuf)-bcnt, + " (%u",hdw->cmd_buffer[1]); + bcnt += ccnt; + if (args > 1) { + ccnt = scnprintf(tbuf+bcnt, + sizeof(tbuf)-bcnt, + ",%u",hdw->cmd_buffer[2]); + bcnt += ccnt; + } + ccnt = scnprintf(tbuf+bcnt, + sizeof(tbuf)-bcnt, + ")"); + bcnt += ccnt; + } + pvr2_trace(PVR2_TRACE_INIT,"%.*s",bcnt,tbuf); + } + ret = pvr2_send_request(hdw,hdw->cmd_buffer,cnt,NULL,0); + LOCK_GIVE(hdw->ctl_lock); + return ret; +} + + +int pvr2_write_register(struct pvr2_hdw *hdw, u16 reg, u32 data) +{ + int ret; + + LOCK_TAKE(hdw->ctl_lock); + + hdw->cmd_buffer[0] = FX2CMD_REG_WRITE; /* write register prefix */ + PVR2_DECOMPOSE_LE(hdw->cmd_buffer,1,data); + hdw->cmd_buffer[5] = 0; + hdw->cmd_buffer[6] = (reg >> 8) & 0xff; + hdw->cmd_buffer[7] = reg & 0xff; + + + ret = pvr2_send_request(hdw, hdw->cmd_buffer, 8, hdw->cmd_buffer, 0); + + LOCK_GIVE(hdw->ctl_lock); + + return ret; +} + + +static int pvr2_read_register(struct pvr2_hdw *hdw, u16 reg, u32 *data) +{ + int ret = 0; + + LOCK_TAKE(hdw->ctl_lock); + + hdw->cmd_buffer[0] = FX2CMD_REG_READ; /* read register prefix */ + hdw->cmd_buffer[1] = 0; + hdw->cmd_buffer[2] = 0; + hdw->cmd_buffer[3] = 0; + hdw->cmd_buffer[4] = 0; + hdw->cmd_buffer[5] = 0; + hdw->cmd_buffer[6] = (reg >> 8) & 0xff; + hdw->cmd_buffer[7] = reg & 0xff; + + ret |= pvr2_send_request(hdw, hdw->cmd_buffer, 8, hdw->cmd_buffer, 4); + *data = PVR2_COMPOSE_LE(hdw->cmd_buffer,0); + + LOCK_GIVE(hdw->ctl_lock); + + return ret; +} + + +void pvr2_hdw_render_useless(struct pvr2_hdw *hdw) +{ + if (!hdw->flag_ok) return; + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Device being rendered inoperable"); + if (hdw->vid_stream) { + pvr2_stream_setup(hdw->vid_stream,NULL,0,0); + } + hdw->flag_ok = 0; + trace_stbit("flag_ok",hdw->flag_ok); + pvr2_hdw_state_sched(hdw); +} + + +void pvr2_hdw_device_reset(struct pvr2_hdw *hdw) +{ + int ret; + pvr2_trace(PVR2_TRACE_INIT,"Performing a device reset..."); + ret = usb_lock_device_for_reset(hdw->usb_dev,NULL); + if (ret == 0) { + ret = usb_reset_device(hdw->usb_dev); + usb_unlock_device(hdw->usb_dev); + } else { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to lock USB device ret=%d",ret); + } + if (init_pause_msec) { + pvr2_trace(PVR2_TRACE_INFO, + "Waiting %u msec for hardware to settle", + init_pause_msec); + msleep(init_pause_msec); + } + +} + + +void pvr2_hdw_cpureset_assert(struct pvr2_hdw *hdw,int val) +{ + char *da; + unsigned int pipe; + int ret; + + if (!hdw->usb_dev) return; + + da = kmalloc(16, GFP_KERNEL); + + if (da == NULL) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Unable to allocate memory to control CPU reset"); + return; + } + + pvr2_trace(PVR2_TRACE_INIT,"cpureset_assert(%d)",val); + + da[0] = val ? 0x01 : 0x00; + + /* Write the CPUCS register on the 8051. The lsb of the register + is the reset bit; a 1 asserts reset while a 0 clears it. */ + pipe = usb_sndctrlpipe(hdw->usb_dev, 0); + ret = usb_control_msg(hdw->usb_dev,pipe,0xa0,0x40,0xe600,0,da,1,1000); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "cpureset_assert(%d) error=%d",val,ret); + pvr2_hdw_render_useless(hdw); + } + + kfree(da); +} + + +int pvr2_hdw_cmd_deep_reset(struct pvr2_hdw *hdw) +{ + return pvr2_issue_simple_cmd(hdw,FX2CMD_DEEP_RESET); +} + + +int pvr2_hdw_cmd_powerup(struct pvr2_hdw *hdw) +{ + return pvr2_issue_simple_cmd(hdw,FX2CMD_POWER_ON); +} + + + +int pvr2_hdw_cmd_decoder_reset(struct pvr2_hdw *hdw) +{ + pvr2_trace(PVR2_TRACE_INIT, + "Requesting decoder reset"); + if (hdw->decoder_client_id) { + v4l2_device_call_all(&hdw->v4l2_dev, hdw->decoder_client_id, + core, reset, 0); + pvr2_hdw_cx25840_vbi_hack(hdw); + return 0; + } + pvr2_trace(PVR2_TRACE_INIT, + "Unable to reset decoder: nothing attached"); + return -ENOTTY; +} + + +static int pvr2_hdw_cmd_hcw_demod_reset(struct pvr2_hdw *hdw, int onoff) +{ + hdw->flag_ok = !0; + + /* Use this for Hauppauge 160xxx only */ + if (le16_to_cpu(hdw->usb_dev->descriptor.idVendor) == 0x2040 && + (le16_to_cpu(hdw->usb_dev->descriptor.idProduct) == 0x7502 || + le16_to_cpu(hdw->usb_dev->descriptor.idProduct) == 0x7510)) { + pr_debug("%s(): resetting demod on Hauppauge 160xxx platform skipped\n", + __func__); + /* Can't reset 160xxx or it will trash Demod tristate */ + return pvr2_issue_simple_cmd(hdw, + FX2CMD_HCW_MAKO_SLEEP_PIN | + (1 << 8) | + ((onoff ? 1 : 0) << 16)); + } + + return pvr2_issue_simple_cmd(hdw, + FX2CMD_HCW_DEMOD_RESETIN | + (1 << 8) | + ((onoff ? 1 : 0) << 16)); +} + + +static int pvr2_hdw_cmd_onair_fe_power_ctrl(struct pvr2_hdw *hdw, int onoff) +{ + hdw->flag_ok = !0; + return pvr2_issue_simple_cmd(hdw,(onoff ? + FX2CMD_ONAIR_DTV_POWER_ON : + FX2CMD_ONAIR_DTV_POWER_OFF)); +} + + +static int pvr2_hdw_cmd_onair_digital_path_ctrl(struct pvr2_hdw *hdw, + int onoff) +{ + return pvr2_issue_simple_cmd(hdw,(onoff ? + FX2CMD_ONAIR_DTV_STREAMING_ON : + FX2CMD_ONAIR_DTV_STREAMING_OFF)); +} + + +static void pvr2_hdw_cmd_modeswitch(struct pvr2_hdw *hdw,int digitalFl) +{ + int cmode; + /* Compare digital/analog desired setting with current setting. If + they don't match, fix it... */ + cmode = (digitalFl ? PVR2_PATHWAY_DIGITAL : PVR2_PATHWAY_ANALOG); + if (cmode == hdw->pathway_state) { + /* They match; nothing to do */ + return; + } + + switch (hdw->hdw_desc->digital_control_scheme) { + case PVR2_DIGITAL_SCHEME_HAUPPAUGE: + pvr2_hdw_cmd_hcw_demod_reset(hdw,digitalFl); + if (cmode == PVR2_PATHWAY_ANALOG) { + /* If moving to analog mode, also force the decoder + to reset. If no decoder is attached, then it's + ok to ignore this because if/when the decoder + attaches, it will reset itself at that time. */ + pvr2_hdw_cmd_decoder_reset(hdw); + } + break; + case PVR2_DIGITAL_SCHEME_ONAIR: + /* Supposedly we should always have the power on whether in + digital or analog mode. But for now do what appears to + work... */ + pvr2_hdw_cmd_onair_fe_power_ctrl(hdw,digitalFl); + break; + default: break; + } + + pvr2_hdw_untrip_unlocked(hdw); + hdw->pathway_state = cmode; +} + + +static void pvr2_led_ctrl_hauppauge(struct pvr2_hdw *hdw, int onoff) +{ + /* change some GPIO data + * + * note: bit d7 of dir appears to control the LED, + * so we shut it off here. + * + */ + if (onoff) { + pvr2_hdw_gpio_chg_dir(hdw, 0xffffffff, 0x00000481); + } else { + pvr2_hdw_gpio_chg_dir(hdw, 0xffffffff, 0x00000401); + } + pvr2_hdw_gpio_chg_out(hdw, 0xffffffff, 0x00000000); +} + + +typedef void (*led_method_func)(struct pvr2_hdw *,int); + +static led_method_func led_methods[] = { + [PVR2_LED_SCHEME_HAUPPAUGE] = pvr2_led_ctrl_hauppauge, +}; + + +/* Toggle LED */ +static void pvr2_led_ctrl(struct pvr2_hdw *hdw,int onoff) +{ + unsigned int scheme_id; + led_method_func fp; + + if ((!onoff) == (!hdw->led_on)) return; + + hdw->led_on = onoff != 0; + + scheme_id = hdw->hdw_desc->led_scheme; + if (scheme_id < ARRAY_SIZE(led_methods)) { + fp = led_methods[scheme_id]; + } else { + fp = NULL; + } + + if (fp) (*fp)(hdw,onoff); +} + + +/* Stop / start video stream transport */ +static int pvr2_hdw_cmd_usbstream(struct pvr2_hdw *hdw,int runFl) +{ + int ret; + + /* If we're in analog mode, then just issue the usual analog + command. */ + if (hdw->pathway_state == PVR2_PATHWAY_ANALOG) { + return pvr2_issue_simple_cmd(hdw, + (runFl ? + FX2CMD_STREAMING_ON : + FX2CMD_STREAMING_OFF)); + /*Note: Not reached */ + } + + if (hdw->pathway_state != PVR2_PATHWAY_DIGITAL) { + /* Whoops, we don't know what mode we're in... */ + return -EINVAL; + } + + /* To get here we have to be in digital mode. The mechanism here + is unfortunately different for different vendors. So we switch + on the device's digital scheme attribute in order to figure out + what to do. */ + switch (hdw->hdw_desc->digital_control_scheme) { + case PVR2_DIGITAL_SCHEME_HAUPPAUGE: + return pvr2_issue_simple_cmd(hdw, + (runFl ? + FX2CMD_HCW_DTV_STREAMING_ON : + FX2CMD_HCW_DTV_STREAMING_OFF)); + case PVR2_DIGITAL_SCHEME_ONAIR: + ret = pvr2_issue_simple_cmd(hdw, + (runFl ? + FX2CMD_STREAMING_ON : + FX2CMD_STREAMING_OFF)); + if (ret) return ret; + return pvr2_hdw_cmd_onair_digital_path_ctrl(hdw,runFl); + default: + return -EINVAL; + } +} + + +/* Evaluate whether or not state_pathway_ok can change */ +static int state_eval_pathway_ok(struct pvr2_hdw *hdw) +{ + if (hdw->state_pathway_ok) { + /* Nothing to do if pathway is already ok */ + return 0; + } + if (!hdw->state_pipeline_idle) { + /* Not allowed to change anything if pipeline is not idle */ + return 0; + } + pvr2_hdw_cmd_modeswitch(hdw,hdw->input_val == PVR2_CVAL_INPUT_DTV); + hdw->state_pathway_ok = !0; + trace_stbit("state_pathway_ok",hdw->state_pathway_ok); + return !0; +} + + +/* Evaluate whether or not state_encoder_ok can change */ +static int state_eval_encoder_ok(struct pvr2_hdw *hdw) +{ + if (hdw->state_encoder_ok) return 0; + if (hdw->flag_tripped) return 0; + if (hdw->state_encoder_run) return 0; + if (hdw->state_encoder_config) return 0; + if (hdw->state_decoder_run) return 0; + if (hdw->state_usbstream_run) return 0; + if (hdw->pathway_state == PVR2_PATHWAY_DIGITAL) { + if (!hdw->hdw_desc->flag_digital_requires_cx23416) return 0; + } else if (hdw->pathway_state != PVR2_PATHWAY_ANALOG) { + return 0; + } + + if (pvr2_upload_firmware2(hdw) < 0) { + hdw->flag_tripped = !0; + trace_stbit("flag_tripped",hdw->flag_tripped); + return !0; + } + hdw->state_encoder_ok = !0; + trace_stbit("state_encoder_ok",hdw->state_encoder_ok); + return !0; +} + + +/* Evaluate whether or not state_encoder_config can change */ +static int state_eval_encoder_config(struct pvr2_hdw *hdw) +{ + if (hdw->state_encoder_config) { + if (hdw->state_encoder_ok) { + if (hdw->state_pipeline_req && + !hdw->state_pipeline_pause) return 0; + } + hdw->state_encoder_config = 0; + hdw->state_encoder_waitok = 0; + trace_stbit("state_encoder_waitok",hdw->state_encoder_waitok); + /* paranoia - solve race if timer just completed */ + del_timer_sync(&hdw->encoder_wait_timer); + } else { + if (!hdw->state_pathway_ok || + (hdw->pathway_state != PVR2_PATHWAY_ANALOG) || + !hdw->state_encoder_ok || + !hdw->state_pipeline_idle || + hdw->state_pipeline_pause || + !hdw->state_pipeline_req || + !hdw->state_pipeline_config) { + /* We must reset the enforced wait interval if + anything has happened that might have disturbed + the encoder. This should be a rare case. */ + if (timer_pending(&hdw->encoder_wait_timer)) { + del_timer_sync(&hdw->encoder_wait_timer); + } + if (hdw->state_encoder_waitok) { + /* Must clear the state - therefore we did + something to a state bit and must also + return true. */ + hdw->state_encoder_waitok = 0; + trace_stbit("state_encoder_waitok", + hdw->state_encoder_waitok); + return !0; + } + return 0; + } + if (!hdw->state_encoder_waitok) { + if (!timer_pending(&hdw->encoder_wait_timer)) { + /* waitok flag wasn't set and timer isn't + running. Check flag once more to avoid + a race then start the timer. This is + the point when we measure out a minimal + quiet interval before doing something to + the encoder. */ + if (!hdw->state_encoder_waitok) { + hdw->encoder_wait_timer.expires = + jiffies + msecs_to_jiffies( + TIME_MSEC_ENCODER_WAIT); + add_timer(&hdw->encoder_wait_timer); + } + } + /* We can't continue until we know we have been + quiet for the interval measured by this + timer. */ + return 0; + } + pvr2_encoder_configure(hdw); + if (hdw->state_encoder_ok) hdw->state_encoder_config = !0; + } + trace_stbit("state_encoder_config",hdw->state_encoder_config); + return !0; +} + + +/* Return true if the encoder should not be running. */ +static int state_check_disable_encoder_run(struct pvr2_hdw *hdw) +{ + if (!hdw->state_encoder_ok) { + /* Encoder isn't healthy at the moment, so stop it. */ + return !0; + } + if (!hdw->state_pathway_ok) { + /* Mode is not understood at the moment (i.e. it wants to + change), so encoder must be stopped. */ + return !0; + } + + switch (hdw->pathway_state) { + case PVR2_PATHWAY_ANALOG: + if (!hdw->state_decoder_run) { + /* We're in analog mode and the decoder is not + running; thus the encoder should be stopped as + well. */ + return !0; + } + break; + case PVR2_PATHWAY_DIGITAL: + if (hdw->state_encoder_runok) { + /* This is a funny case. We're in digital mode so + really the encoder should be stopped. However + if it really is running, only kill it after + runok has been set. This gives a chance for the + onair quirk to function (encoder must run + briefly first, at least once, before onair + digital streaming can work). */ + return !0; + } + break; + default: + /* Unknown mode; so encoder should be stopped. */ + return !0; + } + + /* If we get here, we haven't found a reason to stop the + encoder. */ + return 0; +} + + +/* Return true if the encoder should be running. */ +static int state_check_enable_encoder_run(struct pvr2_hdw *hdw) +{ + if (!hdw->state_encoder_ok) { + /* Don't run the encoder if it isn't healthy... */ + return 0; + } + if (!hdw->state_pathway_ok) { + /* Don't run the encoder if we don't (yet) know what mode + we need to be in... */ + return 0; + } + + switch (hdw->pathway_state) { + case PVR2_PATHWAY_ANALOG: + if (hdw->state_decoder_run && hdw->state_decoder_ready) { + /* In analog mode, if the decoder is running, then + run the encoder. */ + return !0; + } + break; + case PVR2_PATHWAY_DIGITAL: + if ((hdw->hdw_desc->digital_control_scheme == + PVR2_DIGITAL_SCHEME_ONAIR) && + !hdw->state_encoder_runok) { + /* This is a quirk. OnAir hardware won't stream + digital until the encoder has been run at least + once, for a minimal period of time (empiricially + measured to be 1/4 second). So if we're on + OnAir hardware and the encoder has never been + run at all, then start the encoder. Normal + state machine logic in the driver will + automatically handle the remaining bits. */ + return !0; + } + break; + default: + /* For completeness (unknown mode; encoder won't run ever) */ + break; + } + /* If we get here, then we haven't found any reason to run the + encoder, so don't run it. */ + return 0; +} + + +/* Evaluate whether or not state_encoder_run can change */ +static int state_eval_encoder_run(struct pvr2_hdw *hdw) +{ + if (hdw->state_encoder_run) { + if (!state_check_disable_encoder_run(hdw)) return 0; + if (hdw->state_encoder_ok) { + del_timer_sync(&hdw->encoder_run_timer); + if (pvr2_encoder_stop(hdw) < 0) return !0; + } + hdw->state_encoder_run = 0; + } else { + if (!state_check_enable_encoder_run(hdw)) return 0; + if (pvr2_encoder_start(hdw) < 0) return !0; + hdw->state_encoder_run = !0; + if (!hdw->state_encoder_runok) { + hdw->encoder_run_timer.expires = jiffies + + msecs_to_jiffies(TIME_MSEC_ENCODER_OK); + add_timer(&hdw->encoder_run_timer); + } + } + trace_stbit("state_encoder_run",hdw->state_encoder_run); + return !0; +} + + +/* Timeout function for quiescent timer. */ +static void pvr2_hdw_quiescent_timeout(struct timer_list *t) +{ + struct pvr2_hdw *hdw = from_timer(hdw, t, quiescent_timer); + hdw->state_decoder_quiescent = !0; + trace_stbit("state_decoder_quiescent",hdw->state_decoder_quiescent); + hdw->state_stale = !0; + schedule_work(&hdw->workpoll); +} + + +/* Timeout function for decoder stabilization timer. */ +static void pvr2_hdw_decoder_stabilization_timeout(struct timer_list *t) +{ + struct pvr2_hdw *hdw = from_timer(hdw, t, decoder_stabilization_timer); + hdw->state_decoder_ready = !0; + trace_stbit("state_decoder_ready", hdw->state_decoder_ready); + hdw->state_stale = !0; + schedule_work(&hdw->workpoll); +} + + +/* Timeout function for encoder wait timer. */ +static void pvr2_hdw_encoder_wait_timeout(struct timer_list *t) +{ + struct pvr2_hdw *hdw = from_timer(hdw, t, encoder_wait_timer); + hdw->state_encoder_waitok = !0; + trace_stbit("state_encoder_waitok",hdw->state_encoder_waitok); + hdw->state_stale = !0; + schedule_work(&hdw->workpoll); +} + + +/* Timeout function for encoder run timer. */ +static void pvr2_hdw_encoder_run_timeout(struct timer_list *t) +{ + struct pvr2_hdw *hdw = from_timer(hdw, t, encoder_run_timer); + if (!hdw->state_encoder_runok) { + hdw->state_encoder_runok = !0; + trace_stbit("state_encoder_runok",hdw->state_encoder_runok); + hdw->state_stale = !0; + schedule_work(&hdw->workpoll); + } +} + + +/* Evaluate whether or not state_decoder_run can change */ +static int state_eval_decoder_run(struct pvr2_hdw *hdw) +{ + if (hdw->state_decoder_run) { + if (hdw->state_encoder_ok) { + if (hdw->state_pipeline_req && + !hdw->state_pipeline_pause && + hdw->state_pathway_ok) return 0; + } + if (!hdw->flag_decoder_missed) { + pvr2_decoder_enable(hdw,0); + } + hdw->state_decoder_quiescent = 0; + hdw->state_decoder_run = 0; + /* paranoia - solve race if timer(s) just completed */ + del_timer_sync(&hdw->quiescent_timer); + /* Kill the stabilization timer, in case we're killing the + encoder before the previous stabilization interval has + been properly timed. */ + del_timer_sync(&hdw->decoder_stabilization_timer); + hdw->state_decoder_ready = 0; + } else { + if (!hdw->state_decoder_quiescent) { + if (!timer_pending(&hdw->quiescent_timer)) { + /* We don't do something about the + quiescent timer until right here because + we also want to catch cases where the + decoder was already not running (like + after initialization) as opposed to + knowing that we had just stopped it. + The second flag check is here to cover a + race - the timer could have run and set + this flag just after the previous check + but before we did the pending check. */ + if (!hdw->state_decoder_quiescent) { + hdw->quiescent_timer.expires = + jiffies + msecs_to_jiffies( + TIME_MSEC_DECODER_WAIT); + add_timer(&hdw->quiescent_timer); + } + } + /* Don't allow decoder to start again until it has + been quiesced first. This little detail should + hopefully further stabilize the encoder. */ + return 0; + } + if (!hdw->state_pathway_ok || + (hdw->pathway_state != PVR2_PATHWAY_ANALOG) || + !hdw->state_pipeline_req || + hdw->state_pipeline_pause || + !hdw->state_pipeline_config || + !hdw->state_encoder_config || + !hdw->state_encoder_ok) return 0; + del_timer_sync(&hdw->quiescent_timer); + if (hdw->flag_decoder_missed) return 0; + if (pvr2_decoder_enable(hdw,!0) < 0) return 0; + hdw->state_decoder_quiescent = 0; + hdw->state_decoder_ready = 0; + hdw->state_decoder_run = !0; + if (hdw->decoder_client_id == PVR2_CLIENT_ID_SAA7115) { + hdw->decoder_stabilization_timer.expires = + jiffies + msecs_to_jiffies( + TIME_MSEC_DECODER_STABILIZATION_WAIT); + add_timer(&hdw->decoder_stabilization_timer); + } else { + hdw->state_decoder_ready = !0; + } + } + trace_stbit("state_decoder_quiescent",hdw->state_decoder_quiescent); + trace_stbit("state_decoder_run",hdw->state_decoder_run); + trace_stbit("state_decoder_ready", hdw->state_decoder_ready); + return !0; +} + + +/* Evaluate whether or not state_usbstream_run can change */ +static int state_eval_usbstream_run(struct pvr2_hdw *hdw) +{ + if (hdw->state_usbstream_run) { + int fl = !0; + if (hdw->pathway_state == PVR2_PATHWAY_ANALOG) { + fl = (hdw->state_encoder_ok && + hdw->state_encoder_run); + } else if ((hdw->pathway_state == PVR2_PATHWAY_DIGITAL) && + (hdw->hdw_desc->flag_digital_requires_cx23416)) { + fl = hdw->state_encoder_ok; + } + if (fl && + hdw->state_pipeline_req && + !hdw->state_pipeline_pause && + hdw->state_pathway_ok) { + return 0; + } + pvr2_hdw_cmd_usbstream(hdw,0); + hdw->state_usbstream_run = 0; + } else { + if (!hdw->state_pipeline_req || + hdw->state_pipeline_pause || + !hdw->state_pathway_ok) return 0; + if (hdw->pathway_state == PVR2_PATHWAY_ANALOG) { + if (!hdw->state_encoder_ok || + !hdw->state_encoder_run) return 0; + } else if ((hdw->pathway_state == PVR2_PATHWAY_DIGITAL) && + (hdw->hdw_desc->flag_digital_requires_cx23416)) { + if (!hdw->state_encoder_ok) return 0; + if (hdw->state_encoder_run) return 0; + if (hdw->hdw_desc->digital_control_scheme == + PVR2_DIGITAL_SCHEME_ONAIR) { + /* OnAir digital receivers won't stream + unless the analog encoder has run first. + Why? I have no idea. But don't even + try until we know the analog side is + known to have run. */ + if (!hdw->state_encoder_runok) return 0; + } + } + if (pvr2_hdw_cmd_usbstream(hdw,!0) < 0) return 0; + hdw->state_usbstream_run = !0; + } + trace_stbit("state_usbstream_run",hdw->state_usbstream_run); + return !0; +} + + +/* Attempt to configure pipeline, if needed */ +static int state_eval_pipeline_config(struct pvr2_hdw *hdw) +{ + if (hdw->state_pipeline_config || + hdw->state_pipeline_pause) return 0; + pvr2_hdw_commit_execute(hdw); + return !0; +} + + +/* Update pipeline idle and pipeline pause tracking states based on other + inputs. This must be called whenever the other relevant inputs have + changed. */ +static int state_update_pipeline_state(struct pvr2_hdw *hdw) +{ + unsigned int st; + int updatedFl = 0; + /* Update pipeline state */ + st = !(hdw->state_encoder_run || + hdw->state_decoder_run || + hdw->state_usbstream_run || + (!hdw->state_decoder_quiescent)); + if (!st != !hdw->state_pipeline_idle) { + hdw->state_pipeline_idle = st; + updatedFl = !0; + } + if (hdw->state_pipeline_idle && hdw->state_pipeline_pause) { + hdw->state_pipeline_pause = 0; + updatedFl = !0; + } + return updatedFl; +} + + +typedef int (*state_eval_func)(struct pvr2_hdw *); + +/* Set of functions to be run to evaluate various states in the driver. */ +static const state_eval_func eval_funcs[] = { + state_eval_pathway_ok, + state_eval_pipeline_config, + state_eval_encoder_ok, + state_eval_encoder_config, + state_eval_decoder_run, + state_eval_encoder_run, + state_eval_usbstream_run, +}; + + +/* Process various states and return true if we did anything interesting. */ +static int pvr2_hdw_state_update(struct pvr2_hdw *hdw) +{ + unsigned int i; + int state_updated = 0; + int check_flag; + + if (!hdw->state_stale) return 0; + if ((hdw->fw1_state != FW1_STATE_OK) || + !hdw->flag_ok) { + hdw->state_stale = 0; + return !0; + } + /* This loop is the heart of the entire driver. It keeps trying to + evaluate various bits of driver state until nothing changes for + one full iteration. Each "bit of state" tracks some global + aspect of the driver, e.g. whether decoder should run, if + pipeline is configured, usb streaming is on, etc. We separately + evaluate each of those questions based on other driver state to + arrive at the correct running configuration. */ + do { + check_flag = 0; + state_update_pipeline_state(hdw); + /* Iterate over each bit of state */ + for (i = 0; (i<ARRAY_SIZE(eval_funcs)) && hdw->flag_ok; i++) { + if ((*eval_funcs[i])(hdw)) { + check_flag = !0; + state_updated = !0; + state_update_pipeline_state(hdw); + } + } + } while (check_flag && hdw->flag_ok); + hdw->state_stale = 0; + trace_stbit("state_stale",hdw->state_stale); + return state_updated; +} + + +static unsigned int print_input_mask(unsigned int msk, + char *buf,unsigned int acnt) +{ + unsigned int idx,ccnt; + unsigned int tcnt = 0; + for (idx = 0; idx < ARRAY_SIZE(control_values_input); idx++) { + if (!((1UL << idx) & msk)) continue; + ccnt = scnprintf(buf+tcnt, + acnt-tcnt, + "%s%s", + (tcnt ? ", " : ""), + control_values_input[idx]); + tcnt += ccnt; + } + return tcnt; +} + + +static const char *pvr2_pathway_state_name(int id) +{ + switch (id) { + case PVR2_PATHWAY_ANALOG: return "analog"; + case PVR2_PATHWAY_DIGITAL: return "digital"; + default: return "unknown"; + } +} + + +static unsigned int pvr2_hdw_report_unlocked(struct pvr2_hdw *hdw,int which, + char *buf,unsigned int acnt) +{ + switch (which) { + case 0: + return scnprintf( + buf,acnt, + "driver:%s%s%s%s%s <mode=%s>", + (hdw->flag_ok ? " <ok>" : " <fail>"), + (hdw->flag_init_ok ? " <init>" : " <uninitialized>"), + (hdw->flag_disconnected ? " <disconnected>" : + " <connected>"), + (hdw->flag_tripped ? " <tripped>" : ""), + (hdw->flag_decoder_missed ? " <no decoder>" : ""), + pvr2_pathway_state_name(hdw->pathway_state)); + + case 1: + return scnprintf( + buf,acnt, + "pipeline:%s%s%s%s", + (hdw->state_pipeline_idle ? " <idle>" : ""), + (hdw->state_pipeline_config ? + " <configok>" : " <stale>"), + (hdw->state_pipeline_req ? " <req>" : ""), + (hdw->state_pipeline_pause ? " <pause>" : "")); + case 2: + return scnprintf( + buf,acnt, + "worker:%s%s%s%s%s%s%s", + (hdw->state_decoder_run ? + (hdw->state_decoder_ready ? + "<decode:run>" : " <decode:start>") : + (hdw->state_decoder_quiescent ? + "" : " <decode:stop>")), + (hdw->state_decoder_quiescent ? + " <decode:quiescent>" : ""), + (hdw->state_encoder_ok ? + "" : " <encode:init>"), + (hdw->state_encoder_run ? + (hdw->state_encoder_runok ? + " <encode:run>" : + " <encode:firstrun>") : + (hdw->state_encoder_runok ? + " <encode:stop>" : + " <encode:virgin>")), + (hdw->state_encoder_config ? + " <encode:configok>" : + (hdw->state_encoder_waitok ? + "" : " <encode:waitok>")), + (hdw->state_usbstream_run ? + " <usb:run>" : " <usb:stop>"), + (hdw->state_pathway_ok ? + " <pathway:ok>" : "")); + case 3: + return scnprintf( + buf,acnt, + "state: %s", + pvr2_get_state_name(hdw->master_state)); + case 4: { + unsigned int tcnt = 0; + unsigned int ccnt; + + ccnt = scnprintf(buf, + acnt, + "Hardware supported inputs: "); + tcnt += ccnt; + tcnt += print_input_mask(hdw->input_avail_mask, + buf+tcnt, + acnt-tcnt); + if (hdw->input_avail_mask != hdw->input_allowed_mask) { + ccnt = scnprintf(buf+tcnt, + acnt-tcnt, + "; allowed inputs: "); + tcnt += ccnt; + tcnt += print_input_mask(hdw->input_allowed_mask, + buf+tcnt, + acnt-tcnt); + } + return tcnt; + } + case 5: { + struct pvr2_stream_stats stats; + if (!hdw->vid_stream) break; + pvr2_stream_get_stats(hdw->vid_stream, + &stats, + 0); + return scnprintf( + buf,acnt, + "Bytes streamed=%u URBs: queued=%u idle=%u ready=%u processed=%u failed=%u", + stats.bytes_processed, + stats.buffers_in_queue, + stats.buffers_in_idle, + stats.buffers_in_ready, + stats.buffers_processed, + stats.buffers_failed); + } + case 6: { + unsigned int id = hdw->ir_scheme_active; + return scnprintf(buf, acnt, "ir scheme: id=%d %s", id, + (id >= ARRAY_SIZE(ir_scheme_names) ? + "?" : ir_scheme_names[id])); + } + default: break; + } + return 0; +} + + +/* Generate report containing info about attached sub-devices and attached + i2c clients, including an indication of which attached i2c clients are + actually sub-devices. */ +static unsigned int pvr2_hdw_report_clients(struct pvr2_hdw *hdw, + char *buf, unsigned int acnt) +{ + struct v4l2_subdev *sd; + unsigned int tcnt = 0; + unsigned int ccnt; + struct i2c_client *client; + const char *p; + unsigned int id; + + ccnt = scnprintf(buf, acnt, "Associated v4l2-subdev drivers and I2C clients:\n"); + tcnt += ccnt; + v4l2_device_for_each_subdev(sd, &hdw->v4l2_dev) { + id = sd->grp_id; + p = NULL; + if (id < ARRAY_SIZE(module_names)) p = module_names[id]; + if (p) { + ccnt = scnprintf(buf + tcnt, acnt - tcnt, " %s:", p); + tcnt += ccnt; + } else { + ccnt = scnprintf(buf + tcnt, acnt - tcnt, + " (unknown id=%u):", id); + tcnt += ccnt; + } + client = v4l2_get_subdevdata(sd); + if (client) { + ccnt = scnprintf(buf + tcnt, acnt - tcnt, + " %s @ %02x\n", client->name, + client->addr); + tcnt += ccnt; + } else { + ccnt = scnprintf(buf + tcnt, acnt - tcnt, + " no i2c client\n"); + tcnt += ccnt; + } + } + return tcnt; +} + + +unsigned int pvr2_hdw_state_report(struct pvr2_hdw *hdw, + char *buf,unsigned int acnt) +{ + unsigned int bcnt,ccnt,idx; + bcnt = 0; + LOCK_TAKE(hdw->big_lock); + for (idx = 0; ; idx++) { + ccnt = pvr2_hdw_report_unlocked(hdw,idx,buf,acnt); + if (!ccnt) break; + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + if (!acnt) break; + buf[0] = '\n'; ccnt = 1; + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + } + ccnt = pvr2_hdw_report_clients(hdw, buf, acnt); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + LOCK_GIVE(hdw->big_lock); + return bcnt; +} + + +static void pvr2_hdw_state_log_state(struct pvr2_hdw *hdw) +{ + char buf[256]; + unsigned int idx, ccnt; + unsigned int lcnt, ucnt; + + for (idx = 0; ; idx++) { + ccnt = pvr2_hdw_report_unlocked(hdw,idx,buf,sizeof(buf)); + if (!ccnt) break; + pr_info("%s %.*s\n", hdw->name, ccnt, buf); + } + ccnt = pvr2_hdw_report_clients(hdw, buf, sizeof(buf)); + if (ccnt >= sizeof(buf)) + ccnt = sizeof(buf); + + ucnt = 0; + while (ucnt < ccnt) { + lcnt = 0; + while ((lcnt + ucnt < ccnt) && (buf[lcnt + ucnt] != '\n')) { + lcnt++; + } + pr_info("%s %.*s\n", hdw->name, lcnt, buf + ucnt); + ucnt += lcnt + 1; + } +} + + +/* Evaluate and update the driver's current state, taking various actions + as appropriate for the update. */ +static int pvr2_hdw_state_eval(struct pvr2_hdw *hdw) +{ + unsigned int st; + int state_updated = 0; + int callback_flag = 0; + int analog_mode; + + pvr2_trace(PVR2_TRACE_STBITS, + "Drive state check START"); + if (pvrusb2_debug & PVR2_TRACE_STBITS) { + pvr2_hdw_state_log_state(hdw); + } + + /* Process all state and get back over disposition */ + state_updated = pvr2_hdw_state_update(hdw); + + analog_mode = (hdw->pathway_state != PVR2_PATHWAY_DIGITAL); + + /* Update master state based upon all other states. */ + if (!hdw->flag_ok) { + st = PVR2_STATE_DEAD; + } else if (hdw->fw1_state != FW1_STATE_OK) { + st = PVR2_STATE_COLD; + } else if ((analog_mode || + hdw->hdw_desc->flag_digital_requires_cx23416) && + !hdw->state_encoder_ok) { + st = PVR2_STATE_WARM; + } else if (hdw->flag_tripped || + (analog_mode && hdw->flag_decoder_missed)) { + st = PVR2_STATE_ERROR; + } else if (hdw->state_usbstream_run && + (!analog_mode || + (hdw->state_encoder_run && hdw->state_decoder_run))) { + st = PVR2_STATE_RUN; + } else { + st = PVR2_STATE_READY; + } + if (hdw->master_state != st) { + pvr2_trace(PVR2_TRACE_STATE, + "Device state change from %s to %s", + pvr2_get_state_name(hdw->master_state), + pvr2_get_state_name(st)); + pvr2_led_ctrl(hdw,st == PVR2_STATE_RUN); + hdw->master_state = st; + state_updated = !0; + callback_flag = !0; + } + if (state_updated) { + /* Trigger anyone waiting on any state changes here. */ + wake_up(&hdw->state_wait_data); + } + + if (pvrusb2_debug & PVR2_TRACE_STBITS) { + pvr2_hdw_state_log_state(hdw); + } + pvr2_trace(PVR2_TRACE_STBITS, + "Drive state check DONE callback=%d",callback_flag); + + return callback_flag; +} + + +/* Cause kernel thread to check / update driver state */ +static void pvr2_hdw_state_sched(struct pvr2_hdw *hdw) +{ + if (hdw->state_stale) return; + hdw->state_stale = !0; + trace_stbit("state_stale",hdw->state_stale); + schedule_work(&hdw->workpoll); +} + + +int pvr2_hdw_gpio_get_dir(struct pvr2_hdw *hdw,u32 *dp) +{ + return pvr2_read_register(hdw,PVR2_GPIO_DIR,dp); +} + + +int pvr2_hdw_gpio_get_out(struct pvr2_hdw *hdw,u32 *dp) +{ + return pvr2_read_register(hdw,PVR2_GPIO_OUT,dp); +} + + +int pvr2_hdw_gpio_get_in(struct pvr2_hdw *hdw,u32 *dp) +{ + return pvr2_read_register(hdw,PVR2_GPIO_IN,dp); +} + + +int pvr2_hdw_gpio_chg_dir(struct pvr2_hdw *hdw,u32 msk,u32 val) +{ + u32 cval,nval; + int ret; + if (~msk) { + ret = pvr2_read_register(hdw,PVR2_GPIO_DIR,&cval); + if (ret) return ret; + nval = (cval & ~msk) | (val & msk); + pvr2_trace(PVR2_TRACE_GPIO, + "GPIO direction changing 0x%x:0x%x from 0x%x to 0x%x", + msk,val,cval,nval); + } else { + nval = val; + pvr2_trace(PVR2_TRACE_GPIO, + "GPIO direction changing to 0x%x",nval); + } + return pvr2_write_register(hdw,PVR2_GPIO_DIR,nval); +} + + +int pvr2_hdw_gpio_chg_out(struct pvr2_hdw *hdw,u32 msk,u32 val) +{ + u32 cval,nval; + int ret; + if (~msk) { + ret = pvr2_read_register(hdw,PVR2_GPIO_OUT,&cval); + if (ret) return ret; + nval = (cval & ~msk) | (val & msk); + pvr2_trace(PVR2_TRACE_GPIO, + "GPIO output changing 0x%x:0x%x from 0x%x to 0x%x", + msk,val,cval,nval); + } else { + nval = val; + pvr2_trace(PVR2_TRACE_GPIO, + "GPIO output changing to 0x%x",nval); + } + return pvr2_write_register(hdw,PVR2_GPIO_OUT,nval); +} + + +void pvr2_hdw_status_poll(struct pvr2_hdw *hdw) +{ + struct v4l2_tuner *vtp = &hdw->tuner_signal_info; + memset(vtp, 0, sizeof(*vtp)); + vtp->type = (hdw->input_val == PVR2_CVAL_INPUT_RADIO) ? + V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV; + hdw->tuner_signal_stale = 0; + /* Note: There apparently is no replacement for VIDIOC_CROPCAP + using v4l2-subdev - therefore we can't support that AT ALL right + now. (Of course, no sub-drivers seem to implement it either. + But now it's a chicken and egg problem...) */ + v4l2_device_call_all(&hdw->v4l2_dev, 0, tuner, g_tuner, vtp); + pvr2_trace(PVR2_TRACE_CHIPS, "subdev status poll type=%u strength=%u audio=0x%x cap=0x%x low=%u hi=%u", + vtp->type, + vtp->signal, vtp->rxsubchans, vtp->capability, + vtp->rangelow, vtp->rangehigh); + + /* We have to do this to avoid getting into constant polling if + there's nobody to answer a poll of cropcap info. */ + hdw->cropcap_stale = 0; +} + + +unsigned int pvr2_hdw_get_input_available(struct pvr2_hdw *hdw) +{ + return hdw->input_avail_mask; +} + + +unsigned int pvr2_hdw_get_input_allowed(struct pvr2_hdw *hdw) +{ + return hdw->input_allowed_mask; +} + + +static int pvr2_hdw_set_input(struct pvr2_hdw *hdw,int v) +{ + if (hdw->input_val != v) { + hdw->input_val = v; + hdw->input_dirty = !0; + } + + /* Handle side effects - if we switch to a mode that needs the RF + tuner, then select the right frequency choice as well and mark + it dirty. */ + if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) { + hdw->freqSelector = 0; + hdw->freqDirty = !0; + } else if ((hdw->input_val == PVR2_CVAL_INPUT_TV) || + (hdw->input_val == PVR2_CVAL_INPUT_DTV)) { + hdw->freqSelector = 1; + hdw->freqDirty = !0; + } + return 0; +} + + +int pvr2_hdw_set_input_allowed(struct pvr2_hdw *hdw, + unsigned int change_mask, + unsigned int change_val) +{ + int ret = 0; + unsigned int nv,m,idx; + LOCK_TAKE(hdw->big_lock); + do { + nv = hdw->input_allowed_mask & ~change_mask; + nv |= (change_val & change_mask); + nv &= hdw->input_avail_mask; + if (!nv) { + /* No legal modes left; return error instead. */ + ret = -EPERM; + break; + } + hdw->input_allowed_mask = nv; + if ((1UL << hdw->input_val) & hdw->input_allowed_mask) { + /* Current mode is still in the allowed mask, so + we're done. */ + break; + } + /* Select and switch to a mode that is still in the allowed + mask */ + if (!hdw->input_allowed_mask) { + /* Nothing legal; give up */ + break; + } + m = hdw->input_allowed_mask; + for (idx = 0; idx < (sizeof(m) << 3); idx++) { + if (!((1UL << idx) & m)) continue; + pvr2_hdw_set_input(hdw,idx); + break; + } + } while (0); + LOCK_GIVE(hdw->big_lock); + return ret; +} + + +/* Find I2C address of eeprom */ +static int pvr2_hdw_get_eeprom_addr(struct pvr2_hdw *hdw) +{ + int result; + LOCK_TAKE(hdw->ctl_lock); do { + hdw->cmd_buffer[0] = FX2CMD_GET_EEPROM_ADDR; + result = pvr2_send_request(hdw, + hdw->cmd_buffer,1, + hdw->cmd_buffer,1); + if (result < 0) break; + result = hdw->cmd_buffer[0]; + } while(0); LOCK_GIVE(hdw->ctl_lock); + return result; +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw.h b/drivers/media/usb/pvrusb2/pvrusb2-hdw.h new file mode 100644 index 0000000000..860f0a6b09 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw.h @@ -0,0 +1,329 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_HDW_H +#define __PVRUSB2_HDW_H + +#include <linux/usb.h> +#include <linux/videodev2.h> +#include <media/v4l2-dev.h> +#include "pvrusb2-io.h" +#include "pvrusb2-ctrl.h" + + +/* Private internal control ids, look these up with + pvr2_hdw_get_ctrl_by_id() - these are NOT visible in V4L */ +#define PVR2_CID_STDCUR 2 +#define PVR2_CID_STDAVAIL 3 +#define PVR2_CID_INPUT 4 +#define PVR2_CID_AUDIOMODE 5 +#define PVR2_CID_FREQUENCY 6 +#define PVR2_CID_HRES 7 +#define PVR2_CID_VRES 8 +#define PVR2_CID_CROPL 9 +#define PVR2_CID_CROPT 10 +#define PVR2_CID_CROPW 11 +#define PVR2_CID_CROPH 12 +#define PVR2_CID_CROPCAPPAN 13 +#define PVR2_CID_CROPCAPPAD 14 +#define PVR2_CID_CROPCAPBL 15 +#define PVR2_CID_CROPCAPBT 16 +#define PVR2_CID_CROPCAPBW 17 +#define PVR2_CID_CROPCAPBH 18 +#define PVR2_CID_STDDETECT 19 + +/* Legal values for the INPUT state variable */ +#define PVR2_CVAL_INPUT_TV 0 +#define PVR2_CVAL_INPUT_DTV 1 +#define PVR2_CVAL_INPUT_COMPOSITE 2 +#define PVR2_CVAL_INPUT_SVIDEO 3 +#define PVR2_CVAL_INPUT_RADIO 4 +#define PVR2_CVAL_INPUT_MAX PVR2_CVAL_INPUT_RADIO + +enum pvr2_config { + pvr2_config_empty, /* No configuration */ + pvr2_config_mpeg, /* Encoded / compressed video */ + pvr2_config_vbi, /* Standard vbi info */ + pvr2_config_pcm, /* Audio raw pcm stream */ + pvr2_config_rawvideo, /* Video raw frames */ +}; + +enum pvr2_v4l_type { + pvr2_v4l_type_video, + pvr2_v4l_type_vbi, + pvr2_v4l_type_radio, +}; + +/* Major states that we can be in: + * + * DEAD - Device is in an unusable state and cannot be recovered. This + * can happen if we completely lose the ability to communicate with it + * (but it might still on the bus). In this state there's nothing we can + * do; it must be replugged in order to recover. + * + * COLD - Device is in an unusable state, needs microcontroller firmware. + * + * WARM - We can communicate with the device and the proper + * microcontroller firmware is running, but other device initialization is + * still needed (e.g. encoder firmware). + * + * ERROR - A problem prevents capture operation (e.g. encoder firmware + * missing). + * + * READY - Device is operational, but not streaming. + * + * RUN - Device is streaming. + * + */ +#define PVR2_STATE_NONE 0 +#define PVR2_STATE_DEAD 1 +#define PVR2_STATE_COLD 2 +#define PVR2_STATE_WARM 3 +#define PVR2_STATE_ERROR 4 +#define PVR2_STATE_READY 5 +#define PVR2_STATE_RUN 6 + +/* Translate configuration enum to a string label */ +const char *pvr2_config_get_name(enum pvr2_config); + +struct pvr2_hdw; + +/* Create and return a structure for interacting with the underlying + hardware */ +struct pvr2_hdw *pvr2_hdw_create(struct usb_interface *intf, + const struct usb_device_id *devid); + +/* Perform second stage initialization, passing in a notification callback + for when the master state changes. */ +int pvr2_hdw_initialize(struct pvr2_hdw *, + void (*callback_func)(void *), + void *callback_data); + +/* Destroy hardware interaction structure */ +void pvr2_hdw_destroy(struct pvr2_hdw *); + +/* Return true if in the ready (normal) state */ +int pvr2_hdw_dev_ok(struct pvr2_hdw *); + +/* Return small integer number [1..N] for logical instance number of this + device. This is useful for indexing array-valued module parameters. */ +int pvr2_hdw_get_unit_number(struct pvr2_hdw *); + +/* Get pointer to underlying USB device */ +struct usb_device *pvr2_hdw_get_dev(struct pvr2_hdw *); + +/* Retrieve serial number of device */ +unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *); + +/* Retrieve bus location info of device */ +const char *pvr2_hdw_get_bus_info(struct pvr2_hdw *); + +/* Retrieve per-instance string identifier for this specific device */ +const char *pvr2_hdw_get_device_identifier(struct pvr2_hdw *); + +/* Called when hardware has been unplugged */ +void pvr2_hdw_disconnect(struct pvr2_hdw *); + +/* Sets v4l2_dev of a video_device struct */ +void pvr2_hdw_set_v4l2_dev(struct pvr2_hdw *, struct video_device *); + +/* Get the number of defined controls */ +unsigned int pvr2_hdw_get_ctrl_count(struct pvr2_hdw *); + +/* Retrieve a control handle given its index (0..count-1) */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_index(struct pvr2_hdw *,unsigned int); + +/* Retrieve a control handle given its internal ID (if any) */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_id(struct pvr2_hdw *,unsigned int); + +/* Retrieve a control handle given its V4L ID (if any) */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_v4l(struct pvr2_hdw *,unsigned int ctl_id); + +/* Retrieve a control handle given its immediate predecessor V4L ID (if any) */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_nextv4l(struct pvr2_hdw *, + unsigned int ctl_id); + +/* Commit all control changes made up to this point */ +int pvr2_hdw_commit_ctl(struct pvr2_hdw *); + +/* Return a bit mask of valid input selections for this device. Mask bits + * will be according to PVR_CVAL_INPUT_xxxx definitions. */ +unsigned int pvr2_hdw_get_input_available(struct pvr2_hdw *); + +/* Return a bit mask of allowed input selections for this device. Mask bits + * will be according to PVR_CVAL_INPUT_xxxx definitions. */ +unsigned int pvr2_hdw_get_input_allowed(struct pvr2_hdw *); + +/* Change the set of allowed input selections for this device. Both + change_mask and change_valu are mask bits according to + PVR_CVAL_INPUT_xxxx definitions. The change_mask parameter indicate + which settings are being changed and the change_val parameter indicates + whether corresponding settings are being set or cleared. */ +int pvr2_hdw_set_input_allowed(struct pvr2_hdw *, + unsigned int change_mask, + unsigned int change_val); + +/* Return name for this driver instance */ +const char *pvr2_hdw_get_driver_name(struct pvr2_hdw *); + +/* Mark tuner status stale so that it will be re-fetched */ +void pvr2_hdw_execute_tuner_poll(struct pvr2_hdw *); + +/* Return information about the tuner */ +int pvr2_hdw_get_tuner_status(struct pvr2_hdw *,struct v4l2_tuner *); + +/* Return information about cropping capabilities */ +int pvr2_hdw_get_cropcap(struct pvr2_hdw *, struct v4l2_cropcap *); + +/* Query device and see if it thinks it is on a high-speed USB link */ +int pvr2_hdw_is_hsm(struct pvr2_hdw *); + +/* Return a string token representative of the hardware type */ +const char *pvr2_hdw_get_type(struct pvr2_hdw *); + +/* Return a single line description of the hardware type */ +const char *pvr2_hdw_get_desc(struct pvr2_hdw *); + +/* Turn streaming on/off */ +int pvr2_hdw_set_streaming(struct pvr2_hdw *,int); + +/* Find out if streaming is on */ +int pvr2_hdw_get_streaming(struct pvr2_hdw *); + +/* Retrieve driver overall state */ +int pvr2_hdw_get_state(struct pvr2_hdw *); + +/* Configure the type of stream to generate */ +int pvr2_hdw_set_stream_type(struct pvr2_hdw *, enum pvr2_config); + +/* Get handle to video output stream */ +struct pvr2_stream *pvr2_hdw_get_video_stream(struct pvr2_hdw *); + +/* Enable / disable retrieval of CPU firmware or prom contents. This must + be enabled before pvr2_hdw_cpufw_get() will function. Note that doing + this may prevent the device from running (and leaving this mode may + imply a device reset). */ +void pvr2_hdw_cpufw_set_enabled(struct pvr2_hdw *, + int mode, /* 0=8KB FX2, 1=16KB FX2, 2=PROM */ + int enable_flag); + +/* Return true if we're in a mode for retrieval CPU firmware */ +int pvr2_hdw_cpufw_get_enabled(struct pvr2_hdw *); + +/* Retrieve a piece of the CPU's firmware at the given offset. Return + value is the number of bytes retrieved or zero if we're past the end or + an error otherwise (e.g. if firmware retrieval is not enabled). */ +int pvr2_hdw_cpufw_get(struct pvr2_hdw *,unsigned int offs, + char *buf,unsigned int cnt); + +/* Retrieve a previously stored v4l minor device number */ +int pvr2_hdw_v4l_get_minor_number(struct pvr2_hdw *,enum pvr2_v4l_type index); + +/* Store a v4l minor device number */ +void pvr2_hdw_v4l_store_minor_number(struct pvr2_hdw *, + enum pvr2_v4l_type index,int); + +/* The following entry points are all lower level things you normally don't + want to worry about. */ + +/* Issue a command and get a response from the device. LOTS of higher + level stuff is built on this. */ +int pvr2_send_request(struct pvr2_hdw *, + void *write_ptr,unsigned int write_len, + void *read_ptr,unsigned int read_len); + +/* Slightly higher level device communication functions. */ +int pvr2_write_register(struct pvr2_hdw *, u16, u32); + +/* Call if for any reason we can't talk to the hardware anymore - this will + cause the driver to stop flailing on the device. */ +void pvr2_hdw_render_useless(struct pvr2_hdw *); + +/* Set / clear 8051's reset bit */ +void pvr2_hdw_cpureset_assert(struct pvr2_hdw *,int); + +/* Execute a USB-commanded device reset */ +void pvr2_hdw_device_reset(struct pvr2_hdw *); + +/* Reset worker's error trapping circuit breaker */ +int pvr2_hdw_untrip(struct pvr2_hdw *); + +/* Execute hard reset command (after this point it's likely that the + encoder will have to be reconfigured). This also clears the "useless" + state. */ +int pvr2_hdw_cmd_deep_reset(struct pvr2_hdw *); + +/* Execute simple reset command */ +int pvr2_hdw_cmd_powerup(struct pvr2_hdw *); + +/* Order decoder to reset */ +int pvr2_hdw_cmd_decoder_reset(struct pvr2_hdw *); + +/* Direct manipulation of GPIO bits */ +int pvr2_hdw_gpio_get_dir(struct pvr2_hdw *hdw,u32 *); +int pvr2_hdw_gpio_get_out(struct pvr2_hdw *hdw,u32 *); +int pvr2_hdw_gpio_get_in(struct pvr2_hdw *hdw,u32 *); +int pvr2_hdw_gpio_chg_dir(struct pvr2_hdw *hdw,u32 msk,u32 val); +int pvr2_hdw_gpio_chg_out(struct pvr2_hdw *hdw,u32 msk,u32 val); + +/* This data structure is specifically for the next function... */ +struct pvr2_hdw_debug_info { + int big_lock_held; + int ctl_lock_held; + int flag_disconnected; + int flag_init_ok; + int flag_ok; + int fw1_state; + int flag_decoder_missed; + int flag_tripped; + int state_encoder_ok; + int state_encoder_run; + int state_decoder_run; + int state_decoder_ready; + int state_usbstream_run; + int state_decoder_quiescent; + int state_pipeline_config; + int state_pipeline_req; + int state_pipeline_pause; + int state_pipeline_idle; + int cmd_debug_state; + int cmd_debug_write_len; + int cmd_debug_read_len; + int cmd_debug_write_pend; + int cmd_debug_read_pend; + int cmd_debug_timeout; + int cmd_debug_rstatus; + int cmd_debug_wstatus; + unsigned char cmd_code; +}; + +/* Non-intrusively retrieve internal state info - this is useful for + diagnosing lockups. Note that this operation is completed without any + kind of locking and so it is not atomic and may yield inconsistent + results. This is *purely* a debugging aid. */ +void pvr2_hdw_get_debug_info_unlocked(const struct pvr2_hdw *hdw, + struct pvr2_hdw_debug_info *); + +/* Intrusively retrieve internal state info - this is useful for + diagnosing overall driver state. This operation synchronizes against + the overall driver mutex - so if there are locking problems this will + likely hang! This is *purely* a debugging aid. */ +void pvr2_hdw_get_debug_info_locked(struct pvr2_hdw *hdw, + struct pvr2_hdw_debug_info *); + +/* Report out several lines of text that describes driver internal state. + Results are written into the passed-in buffer. */ +unsigned int pvr2_hdw_state_report(struct pvr2_hdw *hdw, + char *buf_ptr,unsigned int buf_size); + +/* Cause modules to log their state once */ +void pvr2_hdw_trigger_module_log(struct pvr2_hdw *hdw); + +/* Cause encoder firmware to be uploaded into the device. This is normally + done autonomously, but the interface is exported here because it is also + a debugging aid. */ +int pvr2_upload_firmware2(struct pvr2_hdw *hdw); + +#endif /* __PVRUSB2_HDW_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c new file mode 100644 index 0000000000..63db04fe12 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c @@ -0,0 +1,655 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <media/i2c/ir-kbd-i2c.h> +#include "pvrusb2-i2c-core.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-fx2-cmd.h" +#include "pvrusb2.h" + +#define trace_i2c(...) pvr2_trace(PVR2_TRACE_I2C,__VA_ARGS__) + +/* + + This module attempts to implement a compliant I2C adapter for the pvrusb2 + device. + +*/ + +static unsigned int i2c_scan; +module_param(i2c_scan, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(i2c_scan,"scan i2c bus at insmod time"); + +static int ir_mode[PVR_NUM] = { [0 ... PVR_NUM-1] = 1 }; +module_param_array(ir_mode, int, NULL, 0444); +MODULE_PARM_DESC(ir_mode,"specify: 0=disable IR reception, 1=normal IR"); + +static int pvr2_disable_ir_video; +module_param_named(disable_autoload_ir_video, pvr2_disable_ir_video, + int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(disable_autoload_ir_video, + "1=do not try to autoload ir_video IR receiver"); + +static int pvr2_i2c_write(struct pvr2_hdw *hdw, /* Context */ + u8 i2c_addr, /* I2C address we're talking to */ + u8 *data, /* Data to write */ + u16 length) /* Size of data to write */ +{ + /* Return value - default 0 means success */ + int ret; + + + if (!data) length = 0; + if (length > (sizeof(hdw->cmd_buffer) - 3)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Killing an I2C write to %u that is too large (desired=%u limit=%u)", + i2c_addr, + length,(unsigned int)(sizeof(hdw->cmd_buffer) - 3)); + return -ENOTSUPP; + } + + LOCK_TAKE(hdw->ctl_lock); + + /* Clear the command buffer (likely to be paranoia) */ + memset(hdw->cmd_buffer, 0, sizeof(hdw->cmd_buffer)); + + /* Set up command buffer for an I2C write */ + hdw->cmd_buffer[0] = FX2CMD_I2C_WRITE; /* write prefix */ + hdw->cmd_buffer[1] = i2c_addr; /* i2c addr of chip */ + hdw->cmd_buffer[2] = length; /* length of what follows */ + if (length) memcpy(hdw->cmd_buffer + 3, data, length); + + /* Do the operation */ + ret = pvr2_send_request(hdw, + hdw->cmd_buffer, + length + 3, + hdw->cmd_buffer, + 1); + if (!ret) { + if (hdw->cmd_buffer[0] != 8) { + ret = -EIO; + if (hdw->cmd_buffer[0] != 7) { + trace_i2c("unexpected status from i2_write[%d]: %d", + i2c_addr,hdw->cmd_buffer[0]); + } + } + } + + LOCK_GIVE(hdw->ctl_lock); + + return ret; +} + +static int pvr2_i2c_read(struct pvr2_hdw *hdw, /* Context */ + u8 i2c_addr, /* I2C address we're talking to */ + u8 *data, /* Data to write */ + u16 dlen, /* Size of data to write */ + u8 *res, /* Where to put data we read */ + u16 rlen) /* Amount of data to read */ +{ + /* Return value - default 0 means success */ + int ret; + + + if (!data) dlen = 0; + if (dlen > (sizeof(hdw->cmd_buffer) - 4)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Killing an I2C read to %u that has wlen too large (desired=%u limit=%u)", + i2c_addr, + dlen,(unsigned int)(sizeof(hdw->cmd_buffer) - 4)); + return -ENOTSUPP; + } + if (res && (rlen > (sizeof(hdw->cmd_buffer) - 1))) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Killing an I2C read to %u that has rlen too large (desired=%u limit=%u)", + i2c_addr, + rlen,(unsigned int)(sizeof(hdw->cmd_buffer) - 1)); + return -ENOTSUPP; + } + + LOCK_TAKE(hdw->ctl_lock); + + /* Clear the command buffer (likely to be paranoia) */ + memset(hdw->cmd_buffer, 0, sizeof(hdw->cmd_buffer)); + + /* Set up command buffer for an I2C write followed by a read */ + hdw->cmd_buffer[0] = FX2CMD_I2C_READ; /* read prefix */ + hdw->cmd_buffer[1] = dlen; /* arg length */ + hdw->cmd_buffer[2] = rlen; /* answer length. Device will send one + more byte (status). */ + hdw->cmd_buffer[3] = i2c_addr; /* i2c addr of chip */ + if (dlen) memcpy(hdw->cmd_buffer + 4, data, dlen); + + /* Do the operation */ + ret = pvr2_send_request(hdw, + hdw->cmd_buffer, + 4 + dlen, + hdw->cmd_buffer, + rlen + 1); + if (!ret) { + if (hdw->cmd_buffer[0] != 8) { + ret = -EIO; + if (hdw->cmd_buffer[0] != 7) { + trace_i2c("unexpected status from i2_read[%d]: %d", + i2c_addr,hdw->cmd_buffer[0]); + } + } + } + + /* Copy back the result */ + if (res && rlen) { + if (ret) { + /* Error, just blank out the return buffer */ + memset(res, 0, rlen); + } else { + memcpy(res, hdw->cmd_buffer + 1, rlen); + } + } + + LOCK_GIVE(hdw->ctl_lock); + + return ret; +} + +/* This is the common low level entry point for doing I2C operations to the + hardware. */ +static int pvr2_i2c_basic_op(struct pvr2_hdw *hdw, + u8 i2c_addr, + u8 *wdata, + u16 wlen, + u8 *rdata, + u16 rlen) +{ + if (!rdata) rlen = 0; + if (!wdata) wlen = 0; + if (rlen || !wlen) { + return pvr2_i2c_read(hdw,i2c_addr,wdata,wlen,rdata,rlen); + } else { + return pvr2_i2c_write(hdw,i2c_addr,wdata,wlen); + } +} + + +/* This is a special entry point for cases of I2C transaction attempts to + the IR receiver. The implementation here simulates the IR receiver by + issuing a command to the FX2 firmware and using that response to return + what the real I2C receiver would have returned. We use this for 24xxx + devices, where the IR receiver chip has been removed and replaced with + FX2 related logic. */ +static int i2c_24xxx_ir(struct pvr2_hdw *hdw, + u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen) +{ + u8 dat[4]; + unsigned int stat; + + if (!(rlen || wlen)) { + /* This is a probe attempt. Just let it succeed. */ + return 0; + } + + /* We don't understand this kind of transaction */ + if ((wlen != 0) || (rlen == 0)) return -EIO; + + if (rlen < 3) { + /* Mike Isely <isely@pobox.com> Appears to be a probe + attempt from lirc. Just fill in zeroes and return. If + we try instead to do the full transaction here, then bad + things seem to happen within the lirc driver module + (version 0.8.0-7 sources from Debian, when run under + vanilla 2.6.17.6 kernel) - and I don't have the patience + to chase it down. */ + if (rlen > 0) rdata[0] = 0; + if (rlen > 1) rdata[1] = 0; + return 0; + } + + /* Issue a command to the FX2 to read the IR receiver. */ + LOCK_TAKE(hdw->ctl_lock); do { + hdw->cmd_buffer[0] = FX2CMD_GET_IR_CODE; + stat = pvr2_send_request(hdw, + hdw->cmd_buffer,1, + hdw->cmd_buffer,4); + dat[0] = hdw->cmd_buffer[0]; + dat[1] = hdw->cmd_buffer[1]; + dat[2] = hdw->cmd_buffer[2]; + dat[3] = hdw->cmd_buffer[3]; + } while (0); LOCK_GIVE(hdw->ctl_lock); + + /* Give up if that operation failed. */ + if (stat != 0) return stat; + + /* Mangle the results into something that looks like the real IR + receiver. */ + rdata[2] = 0xc1; + if (dat[0] != 1) { + /* No code received. */ + rdata[0] = 0; + rdata[1] = 0; + } else { + u16 val; + /* Mash the FX2 firmware-provided IR code into something + that the normal i2c chip-level driver expects. */ + val = dat[1]; + val <<= 8; + val |= dat[2]; + val >>= 1; + val &= ~0x0003; + val |= 0x8000; + rdata[0] = (val >> 8) & 0xffu; + rdata[1] = val & 0xffu; + } + + return 0; +} + +/* This is a special entry point that is entered if an I2C operation is + attempted to a wm8775 chip on model 24xxx hardware. Autodetect of this + part doesn't work, but we know it is really there. So let's look for + the autodetect attempt and just return success if we see that. */ +static int i2c_hack_wm8775(struct pvr2_hdw *hdw, + u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen) +{ + if (!(rlen || wlen)) { + // This is a probe attempt. Just let it succeed. + return 0; + } + return pvr2_i2c_basic_op(hdw,i2c_addr,wdata,wlen,rdata,rlen); +} + +/* This is an entry point designed to always fail any attempt to perform a + transfer. We use this to cause certain I2C addresses to not be + probed. */ +static int i2c_black_hole(struct pvr2_hdw *hdw, + u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen) +{ + return -EIO; +} + +/* This is a special entry point that is entered if an I2C operation is + attempted to a cx25840 chip on model 24xxx hardware. This chip can + sometimes wedge itself. Worse still, when this happens msp3400 can + falsely detect this part and then the system gets hosed up after msp3400 + gets confused and dies. What we want to do here is try to keep msp3400 + away and also try to notice if the chip is wedged and send a warning to + the system log. */ +static int i2c_hack_cx25840(struct pvr2_hdw *hdw, + u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen) +{ + int ret; + unsigned int subaddr; + u8 wbuf[2]; + int state = hdw->i2c_cx25840_hack_state; + + if (!(rlen || wlen)) { + // Probe attempt - always just succeed and don't bother the + // hardware (this helps to make the state machine further + // down somewhat easier). + return 0; + } + + if (state == 3) { + return pvr2_i2c_basic_op(hdw,i2c_addr,wdata,wlen,rdata,rlen); + } + + /* We're looking for the exact pattern where the revision register + is being read. The cx25840 module will always look at the + revision register first. Any other pattern of access therefore + has to be a probe attempt from somebody else so we'll reject it. + Normally we could just let each client just probe the part + anyway, but when the cx25840 is wedged, msp3400 will get a false + positive and that just screws things up... */ + + if (wlen == 0) { + switch (state) { + case 1: subaddr = 0x0100; break; + case 2: subaddr = 0x0101; break; + default: goto fail; + } + } else if (wlen == 2) { + subaddr = (wdata[0] << 8) | wdata[1]; + switch (subaddr) { + case 0x0100: state = 1; break; + case 0x0101: state = 2; break; + default: goto fail; + } + } else { + goto fail; + } + if (!rlen) goto success; + state = 0; + if (rlen != 1) goto fail; + + /* If we get to here then we have a legitimate read for one of the + two revision bytes, so pass it through. */ + wbuf[0] = subaddr >> 8; + wbuf[1] = subaddr; + ret = pvr2_i2c_basic_op(hdw,i2c_addr,wbuf,2,rdata,rlen); + + if ((ret != 0) || (*rdata == 0x04) || (*rdata == 0x0a)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "***WARNING*** Detected a wedged cx25840 chip; the device will not work."); + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "***WARNING*** Try power cycling the pvrusb2 device."); + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "***WARNING*** Disabling further access to the device to prevent other foul-ups."); + // This blocks all further communication with the part. + hdw->i2c_func[0x44] = NULL; + pvr2_hdw_render_useless(hdw); + goto fail; + } + + /* Success! */ + pvr2_trace(PVR2_TRACE_CHIPS,"cx25840 appears to be OK."); + state = 3; + + success: + hdw->i2c_cx25840_hack_state = state; + return 0; + + fail: + hdw->i2c_cx25840_hack_state = state; + return -EIO; +} + +/* This is a very, very limited I2C adapter implementation. We can only + support what we actually know will work on the device... */ +static int pvr2_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg msgs[], + int num) +{ + int ret = -ENOTSUPP; + pvr2_i2c_func funcp = NULL; + struct pvr2_hdw *hdw = (struct pvr2_hdw *)(i2c_adap->algo_data); + + if (!num) { + ret = -EINVAL; + goto done; + } + if (msgs[0].addr < PVR2_I2C_FUNC_CNT) { + funcp = hdw->i2c_func[msgs[0].addr]; + } + if (!funcp) { + ret = -EIO; + goto done; + } + + if (num == 1) { + if (msgs[0].flags & I2C_M_RD) { + /* Simple read */ + u16 tcnt,bcnt,offs; + if (!msgs[0].len) { + /* Length == 0 read. This is a probe. */ + if (funcp(hdw,msgs[0].addr,NULL,0,NULL,0)) { + ret = -EIO; + goto done; + } + ret = 1; + goto done; + } + /* If the read is short enough we'll do the whole + thing atomically. Otherwise we have no choice + but to break apart the reads. */ + tcnt = msgs[0].len; + offs = 0; + while (tcnt) { + bcnt = tcnt; + if (bcnt > sizeof(hdw->cmd_buffer)-1) { + bcnt = sizeof(hdw->cmd_buffer)-1; + } + if (funcp(hdw,msgs[0].addr,NULL,0, + msgs[0].buf+offs,bcnt)) { + ret = -EIO; + goto done; + } + offs += bcnt; + tcnt -= bcnt; + } + ret = 1; + goto done; + } else { + /* Simple write */ + ret = 1; + if (funcp(hdw,msgs[0].addr, + msgs[0].buf,msgs[0].len,NULL,0)) { + ret = -EIO; + } + goto done; + } + } else if (num == 2) { + if (msgs[0].addr != msgs[1].addr) { + trace_i2c("i2c refusing 2 phase transfer with conflicting target addresses"); + ret = -ENOTSUPP; + goto done; + } + if ((!((msgs[0].flags & I2C_M_RD))) && + (msgs[1].flags & I2C_M_RD)) { + u16 tcnt,bcnt,wcnt,offs; + /* Write followed by atomic read. If the read + portion is short enough we'll do the whole thing + atomically. Otherwise we have no choice but to + break apart the reads. */ + tcnt = msgs[1].len; + wcnt = msgs[0].len; + offs = 0; + while (tcnt || wcnt) { + bcnt = tcnt; + if (bcnt > sizeof(hdw->cmd_buffer)-1) { + bcnt = sizeof(hdw->cmd_buffer)-1; + } + if (funcp(hdw,msgs[0].addr, + msgs[0].buf,wcnt, + msgs[1].buf+offs,bcnt)) { + ret = -EIO; + goto done; + } + offs += bcnt; + tcnt -= bcnt; + wcnt = 0; + } + ret = 2; + goto done; + } else { + trace_i2c("i2c refusing complex transfer read0=%d read1=%d", + (msgs[0].flags & I2C_M_RD), + (msgs[1].flags & I2C_M_RD)); + } + } else { + trace_i2c("i2c refusing %d phase transfer",num); + } + + done: + if (pvrusb2_debug & PVR2_TRACE_I2C_TRAF) { + unsigned int idx,offs,cnt; + for (idx = 0; idx < num; idx++) { + cnt = msgs[idx].len; + pr_info("pvrusb2 i2c xfer %u/%u: addr=0x%x len=%d %s", + idx+1,num, + msgs[idx].addr, + cnt, + (msgs[idx].flags & I2C_M_RD ? + "read" : "write")); + if ((ret > 0) || !(msgs[idx].flags & I2C_M_RD)) { + if (cnt > 8) cnt = 8; + pr_cont(" ["); + for (offs = 0; offs < cnt; offs++) { + if (offs) pr_cont(" "); + pr_cont("%02x", msgs[idx].buf[offs]); + } + if (offs < cnt) pr_cont(" ..."); + pr_cont("]"); + } + if (idx+1 == num) { + pr_cont(" result=%d", ret); + } + pr_cont("\n"); + } + if (!num) { + pr_info("pvrusb2 i2c xfer null transfer result=%d\n", + ret); + } + } + return ret; +} + +static u32 pvr2_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C; +} + +static const struct i2c_algorithm pvr2_i2c_algo_template = { + .master_xfer = pvr2_i2c_xfer, + .functionality = pvr2_i2c_functionality, +}; + +static const struct i2c_adapter pvr2_i2c_adap_template = { + .owner = THIS_MODULE, + .class = 0, +}; + + +/* Return true if device exists at given address */ +static int do_i2c_probe(struct pvr2_hdw *hdw, int addr) +{ + struct i2c_msg msg[1]; + int rc; + msg[0].addr = 0; + msg[0].flags = I2C_M_RD; + msg[0].len = 0; + msg[0].buf = NULL; + msg[0].addr = addr; + rc = i2c_transfer(&hdw->i2c_adap, msg, ARRAY_SIZE(msg)); + return rc == 1; +} + +static void do_i2c_scan(struct pvr2_hdw *hdw) +{ + int i; + pr_info("%s: i2c scan beginning\n", hdw->name); + for (i = 0; i < 128; i++) { + if (do_i2c_probe(hdw, i)) { + pr_info("%s: i2c scan: found device @ 0x%x\n", + hdw->name, i); + } + } + pr_info("%s: i2c scan done.\n", hdw->name); +} + +static void pvr2_i2c_register_ir(struct pvr2_hdw *hdw) +{ + struct i2c_board_info info; + struct IR_i2c_init_data *init_data = &hdw->ir_init_data; + if (pvr2_disable_ir_video) { + pvr2_trace(PVR2_TRACE_INFO, + "Automatic binding of ir_video has been disabled."); + return; + } + memset(&info, 0, sizeof(struct i2c_board_info)); + switch (hdw->ir_scheme_active) { + case PVR2_IR_SCHEME_24XXX: /* FX2-controlled IR */ + case PVR2_IR_SCHEME_29XXX: /* Original 29xxx device */ + init_data->ir_codes = RC_MAP_HAUPPAUGE; + init_data->internal_get_key_func = IR_KBD_GET_KEY_HAUP; + init_data->type = RC_PROTO_BIT_RC5; + init_data->name = hdw->hdw_desc->description; + init_data->polling_interval = 100; /* ms From ir-kbd-i2c */ + /* IR Receiver */ + info.addr = 0x18; + info.platform_data = init_data; + strscpy(info.type, "ir_video", I2C_NAME_SIZE); + pvr2_trace(PVR2_TRACE_INFO, "Binding %s to i2c address 0x%02x.", + info.type, info.addr); + i2c_new_client_device(&hdw->i2c_adap, &info); + break; + case PVR2_IR_SCHEME_ZILOG: /* HVR-1950 style */ + case PVR2_IR_SCHEME_24XXX_MCE: /* 24xxx MCE device */ + init_data->ir_codes = RC_MAP_HAUPPAUGE; + init_data->internal_get_key_func = IR_KBD_GET_KEY_HAUP_XVR; + init_data->type = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_RC6_MCE | + RC_PROTO_BIT_RC6_6A_32; + init_data->name = hdw->hdw_desc->description; + /* IR Transceiver */ + info.addr = 0x71; + info.platform_data = init_data; + strscpy(info.type, "ir_z8f0811_haup", I2C_NAME_SIZE); + pvr2_trace(PVR2_TRACE_INFO, "Binding %s to i2c address 0x%02x.", + info.type, info.addr); + i2c_new_client_device(&hdw->i2c_adap, &info); + break; + default: + /* The device either doesn't support I2C-based IR or we + don't know (yet) how to operate IR on the device. */ + break; + } +} + +void pvr2_i2c_core_init(struct pvr2_hdw *hdw) +{ + unsigned int idx; + + /* The default action for all possible I2C addresses is just to do + the transfer normally. */ + for (idx = 0; idx < PVR2_I2C_FUNC_CNT; idx++) { + hdw->i2c_func[idx] = pvr2_i2c_basic_op; + } + + /* However, deal with various special cases for 24xxx hardware. */ + if (ir_mode[hdw->unit_number] == 0) { + pr_info("%s: IR disabled\n", hdw->name); + hdw->i2c_func[0x18] = i2c_black_hole; + } else if (ir_mode[hdw->unit_number] == 1) { + if (hdw->ir_scheme_active == PVR2_IR_SCHEME_24XXX) { + /* Set up translation so that our IR looks like a + 29xxx device */ + hdw->i2c_func[0x18] = i2c_24xxx_ir; + } + } + if (hdw->hdw_desc->flag_has_cx25840) { + hdw->i2c_func[0x44] = i2c_hack_cx25840; + } + if (hdw->hdw_desc->flag_has_wm8775) { + hdw->i2c_func[0x1b] = i2c_hack_wm8775; + } + + // Configure the adapter and set up everything else related to it. + hdw->i2c_adap = pvr2_i2c_adap_template; + hdw->i2c_algo = pvr2_i2c_algo_template; + strscpy(hdw->i2c_adap.name, hdw->name, sizeof(hdw->i2c_adap.name)); + hdw->i2c_adap.dev.parent = &hdw->usb_dev->dev; + hdw->i2c_adap.algo = &hdw->i2c_algo; + hdw->i2c_adap.algo_data = hdw; + hdw->i2c_linked = !0; + i2c_set_adapdata(&hdw->i2c_adap, &hdw->v4l2_dev); + i2c_add_adapter(&hdw->i2c_adap); + if (hdw->i2c_func[0x18] == i2c_24xxx_ir) { + /* Probe for a different type of IR receiver on this + device. This is really the only way to differentiate + older 24xxx devices from 24xxx variants that include an + IR blaster. If the IR blaster is present, the IR + receiver is part of that chip and thus we must disable + the emulated IR receiver. */ + if (do_i2c_probe(hdw, 0x71)) { + pvr2_trace(PVR2_TRACE_INFO, + "Device has newer IR hardware; disabling unneeded virtual IR device"); + hdw->i2c_func[0x18] = NULL; + /* Remember that this is a different device... */ + hdw->ir_scheme_active = PVR2_IR_SCHEME_24XXX_MCE; + } + } + if (i2c_scan) do_i2c_scan(hdw); + + pvr2_i2c_register_ir(hdw); +} + +void pvr2_i2c_core_done(struct pvr2_hdw *hdw) +{ + if (hdw->i2c_linked) { + i2c_del_adapter(&hdw->i2c_adap); + hdw->i2c_linked = 0; + } +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.h b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.h new file mode 100644 index 0000000000..6da66a80a1 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_I2C_CORE_H +#define __PVRUSB2_I2C_CORE_H + +struct pvr2_hdw; + +void pvr2_i2c_core_init(struct pvr2_hdw *); +void pvr2_i2c_core_done(struct pvr2_hdw *); + + +#endif /* __PVRUSB2_I2C_ADAPTER_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-io.c b/drivers/media/usb/pvrusb2/pvrusb2-io.c new file mode 100644 index 0000000000..675dc7153e --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-io.c @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ + +#include "pvrusb2-io.h" +#include "pvrusb2-debug.h" +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/mutex.h> + +static const char *pvr2_buffer_state_decode(enum pvr2_buffer_state); + +#define BUFFER_SIG 0x47653271 + +// #define SANITY_CHECK_BUFFERS + + +#ifdef SANITY_CHECK_BUFFERS +#define BUFFER_CHECK(bp) do { \ + if ((bp)->signature != BUFFER_SIG) { \ + pvr2_trace(PVR2_TRACE_ERROR_LEGS, \ + "Buffer %p is bad at %s:%d", \ + (bp), __FILE__, __LINE__); \ + pvr2_buffer_describe(bp, "BadSig"); \ + BUG(); \ + } \ +} while (0) +#else +#define BUFFER_CHECK(bp) do {} while (0) +#endif + +struct pvr2_stream { + /* Buffers queued for reading */ + struct list_head queued_list; + unsigned int q_count; + unsigned int q_bcount; + /* Buffers with retrieved data */ + struct list_head ready_list; + unsigned int r_count; + unsigned int r_bcount; + /* Buffers available for use */ + struct list_head idle_list; + unsigned int i_count; + unsigned int i_bcount; + /* Pointers to all buffers */ + struct pvr2_buffer **buffers; + /* Array size of buffers */ + unsigned int buffer_slot_count; + /* Total buffers actually in circulation */ + unsigned int buffer_total_count; + /* Designed number of buffers to be in circulation */ + unsigned int buffer_target_count; + /* Executed when ready list become non-empty */ + pvr2_stream_callback callback_func; + void *callback_data; + /* Context for transfer endpoint */ + struct usb_device *dev; + int endpoint; + /* Overhead for mutex enforcement */ + spinlock_t list_lock; + struct mutex mutex; + /* Tracking state for tolerating errors */ + unsigned int fail_count; + unsigned int fail_tolerance; + + unsigned int buffers_processed; + unsigned int buffers_failed; + unsigned int bytes_processed; +}; + +struct pvr2_buffer { + int id; + int signature; + enum pvr2_buffer_state state; + void *ptr; /* Pointer to storage area */ + unsigned int max_count; /* Size of storage area */ + unsigned int used_count; /* Amount of valid data in storage area */ + int status; /* Transfer result status */ + struct pvr2_stream *stream; + struct list_head list_overhead; + struct urb *purb; +}; + +static const char *pvr2_buffer_state_decode(enum pvr2_buffer_state st) +{ + switch (st) { + case pvr2_buffer_state_none: return "none"; + case pvr2_buffer_state_idle: return "idle"; + case pvr2_buffer_state_queued: return "queued"; + case pvr2_buffer_state_ready: return "ready"; + } + return "unknown"; +} + +#ifdef SANITY_CHECK_BUFFERS +static void pvr2_buffer_describe(struct pvr2_buffer *bp, const char *msg) +{ + pvr2_trace(PVR2_TRACE_INFO, + "buffer%s%s %p state=%s id=%d status=%d stream=%p purb=%p sig=0x%x", + (msg ? " " : ""), + (msg ? msg : ""), + bp, + (bp ? pvr2_buffer_state_decode(bp->state) : "(invalid)"), + (bp ? bp->id : 0), + (bp ? bp->status : 0), + (bp ? bp->stream : NULL), + (bp ? bp->purb : NULL), + (bp ? bp->signature : 0)); +} +#endif /* SANITY_CHECK_BUFFERS */ + +static void pvr2_buffer_remove(struct pvr2_buffer *bp) +{ + unsigned int *cnt; + unsigned int *bcnt; + unsigned int ccnt; + struct pvr2_stream *sp = bp->stream; + switch (bp->state) { + case pvr2_buffer_state_idle: + cnt = &sp->i_count; + bcnt = &sp->i_bcount; + ccnt = bp->max_count; + break; + case pvr2_buffer_state_queued: + cnt = &sp->q_count; + bcnt = &sp->q_bcount; + ccnt = bp->max_count; + break; + case pvr2_buffer_state_ready: + cnt = &sp->r_count; + bcnt = &sp->r_bcount; + ccnt = bp->used_count; + break; + default: + return; + } + list_del_init(&bp->list_overhead); + (*cnt)--; + (*bcnt) -= ccnt; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferPool %8s dec cap=%07d cnt=%02d", + pvr2_buffer_state_decode(bp->state), *bcnt, *cnt); + bp->state = pvr2_buffer_state_none; +} + +static void pvr2_buffer_set_none(struct pvr2_buffer *bp) +{ + unsigned long irq_flags; + struct pvr2_stream *sp; + BUFFER_CHECK(bp); + sp = bp->stream; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferState %p %6s --> %6s", + bp, + pvr2_buffer_state_decode(bp->state), + pvr2_buffer_state_decode(pvr2_buffer_state_none)); + spin_lock_irqsave(&sp->list_lock, irq_flags); + pvr2_buffer_remove(bp); + spin_unlock_irqrestore(&sp->list_lock, irq_flags); +} + +static int pvr2_buffer_set_ready(struct pvr2_buffer *bp) +{ + int fl; + unsigned long irq_flags; + struct pvr2_stream *sp; + BUFFER_CHECK(bp); + sp = bp->stream; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferState %p %6s --> %6s", + bp, + pvr2_buffer_state_decode(bp->state), + pvr2_buffer_state_decode(pvr2_buffer_state_ready)); + spin_lock_irqsave(&sp->list_lock, irq_flags); + fl = (sp->r_count == 0); + pvr2_buffer_remove(bp); + list_add_tail(&bp->list_overhead, &sp->ready_list); + bp->state = pvr2_buffer_state_ready; + (sp->r_count)++; + sp->r_bcount += bp->used_count; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferPool %8s inc cap=%07d cnt=%02d", + pvr2_buffer_state_decode(bp->state), + sp->r_bcount, sp->r_count); + spin_unlock_irqrestore(&sp->list_lock, irq_flags); + return fl; +} + +static void pvr2_buffer_set_idle(struct pvr2_buffer *bp) +{ + unsigned long irq_flags; + struct pvr2_stream *sp; + BUFFER_CHECK(bp); + sp = bp->stream; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferState %p %6s --> %6s", + bp, + pvr2_buffer_state_decode(bp->state), + pvr2_buffer_state_decode(pvr2_buffer_state_idle)); + spin_lock_irqsave(&sp->list_lock, irq_flags); + pvr2_buffer_remove(bp); + list_add_tail(&bp->list_overhead, &sp->idle_list); + bp->state = pvr2_buffer_state_idle; + (sp->i_count)++; + sp->i_bcount += bp->max_count; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferPool %8s inc cap=%07d cnt=%02d", + pvr2_buffer_state_decode(bp->state), + sp->i_bcount, sp->i_count); + spin_unlock_irqrestore(&sp->list_lock, irq_flags); +} + +static void pvr2_buffer_set_queued(struct pvr2_buffer *bp) +{ + unsigned long irq_flags; + struct pvr2_stream *sp; + BUFFER_CHECK(bp); + sp = bp->stream; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferState %p %6s --> %6s", + bp, + pvr2_buffer_state_decode(bp->state), + pvr2_buffer_state_decode(pvr2_buffer_state_queued)); + spin_lock_irqsave(&sp->list_lock, irq_flags); + pvr2_buffer_remove(bp); + list_add_tail(&bp->list_overhead, &sp->queued_list); + bp->state = pvr2_buffer_state_queued; + (sp->q_count)++; + sp->q_bcount += bp->max_count; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferPool %8s inc cap=%07d cnt=%02d", + pvr2_buffer_state_decode(bp->state), + sp->q_bcount, sp->q_count); + spin_unlock_irqrestore(&sp->list_lock, irq_flags); +} + +static void pvr2_buffer_wipe(struct pvr2_buffer *bp) +{ + if (bp->state == pvr2_buffer_state_queued) { + usb_kill_urb(bp->purb); + } +} + +static int pvr2_buffer_init(struct pvr2_buffer *bp, + struct pvr2_stream *sp, + unsigned int id) +{ + memset(bp, 0, sizeof(*bp)); + bp->signature = BUFFER_SIG; + bp->id = id; + pvr2_trace(PVR2_TRACE_BUF_POOL, + "/*---TRACE_FLOW---*/ bufferInit %p stream=%p", bp, sp); + bp->stream = sp; + bp->state = pvr2_buffer_state_none; + INIT_LIST_HEAD(&bp->list_overhead); + bp->purb = usb_alloc_urb(0, GFP_KERNEL); + if (! bp->purb) return -ENOMEM; +#ifdef SANITY_CHECK_BUFFERS + pvr2_buffer_describe(bp, "create"); +#endif + return 0; +} + +static void pvr2_buffer_done(struct pvr2_buffer *bp) +{ +#ifdef SANITY_CHECK_BUFFERS + pvr2_buffer_describe(bp, "delete"); +#endif + pvr2_buffer_wipe(bp); + pvr2_buffer_set_none(bp); + bp->signature = 0; + bp->stream = NULL; + usb_free_urb(bp->purb); + pvr2_trace(PVR2_TRACE_BUF_POOL, "/*---TRACE_FLOW---*/ bufferDone %p", + bp); +} + +static int pvr2_stream_buffer_count(struct pvr2_stream *sp, unsigned int cnt) +{ + int ret; + unsigned int scnt; + + /* Allocate buffers pointer array in multiples of 32 entries */ + if (cnt == sp->buffer_total_count) return 0; + + pvr2_trace(PVR2_TRACE_BUF_POOL, + "/*---TRACE_FLOW---*/ poolResize stream=%p cur=%d adj=%+d", + sp, + sp->buffer_total_count, + cnt-sp->buffer_total_count); + + scnt = cnt & ~0x1f; + if (cnt > scnt) scnt += 0x20; + + if (cnt > sp->buffer_total_count) { + if (scnt > sp->buffer_slot_count) { + struct pvr2_buffer **nb; + + nb = kmalloc_array(scnt, sizeof(*nb), GFP_KERNEL); + if (!nb) return -ENOMEM; + if (sp->buffer_slot_count) { + memcpy(nb, sp->buffers, + sp->buffer_slot_count * sizeof(*nb)); + kfree(sp->buffers); + } + sp->buffers = nb; + sp->buffer_slot_count = scnt; + } + while (sp->buffer_total_count < cnt) { + struct pvr2_buffer *bp; + bp = kmalloc(sizeof(*bp), GFP_KERNEL); + if (!bp) return -ENOMEM; + ret = pvr2_buffer_init(bp, sp, sp->buffer_total_count); + if (ret) { + kfree(bp); + return -ENOMEM; + } + sp->buffers[sp->buffer_total_count] = bp; + (sp->buffer_total_count)++; + pvr2_buffer_set_idle(bp); + } + } else { + while (sp->buffer_total_count > cnt) { + struct pvr2_buffer *bp; + bp = sp->buffers[sp->buffer_total_count - 1]; + /* Paranoia */ + sp->buffers[sp->buffer_total_count - 1] = NULL; + (sp->buffer_total_count)--; + pvr2_buffer_done(bp); + kfree(bp); + } + if (scnt < sp->buffer_slot_count) { + struct pvr2_buffer **nb = NULL; + if (scnt) { + nb = kmemdup(sp->buffers, scnt * sizeof(*nb), + GFP_KERNEL); + if (!nb) return -ENOMEM; + } + kfree(sp->buffers); + sp->buffers = nb; + sp->buffer_slot_count = scnt; + } + } + return 0; +} + +static int pvr2_stream_achieve_buffer_count(struct pvr2_stream *sp) +{ + struct pvr2_buffer *bp; + unsigned int cnt; + + if (sp->buffer_total_count == sp->buffer_target_count) return 0; + + pvr2_trace(PVR2_TRACE_BUF_POOL, + "/*---TRACE_FLOW---*/ poolCheck stream=%p cur=%d tgt=%d", + sp, sp->buffer_total_count, sp->buffer_target_count); + + if (sp->buffer_total_count < sp->buffer_target_count) { + return pvr2_stream_buffer_count(sp, sp->buffer_target_count); + } + + cnt = 0; + while ((sp->buffer_total_count - cnt) > sp->buffer_target_count) { + bp = sp->buffers[sp->buffer_total_count - (cnt + 1)]; + if (bp->state != pvr2_buffer_state_idle) break; + cnt++; + } + if (cnt) { + pvr2_stream_buffer_count(sp, sp->buffer_total_count - cnt); + } + + return 0; +} + +static void pvr2_stream_internal_flush(struct pvr2_stream *sp) +{ + struct list_head *lp; + struct pvr2_buffer *bp1; + while ((lp = sp->queued_list.next) != &sp->queued_list) { + bp1 = list_entry(lp, struct pvr2_buffer, list_overhead); + pvr2_buffer_wipe(bp1); + /* At this point, we should be guaranteed that no + completion callback may happen on this buffer. But it's + possible that it might have completed after we noticed + it but before we wiped it. So double check its status + here first. */ + if (bp1->state != pvr2_buffer_state_queued) continue; + pvr2_buffer_set_idle(bp1); + } + if (sp->buffer_total_count != sp->buffer_target_count) { + pvr2_stream_achieve_buffer_count(sp); + } +} + +static void pvr2_stream_init(struct pvr2_stream *sp) +{ + spin_lock_init(&sp->list_lock); + mutex_init(&sp->mutex); + INIT_LIST_HEAD(&sp->queued_list); + INIT_LIST_HEAD(&sp->ready_list); + INIT_LIST_HEAD(&sp->idle_list); +} + +static void pvr2_stream_done(struct pvr2_stream *sp) +{ + mutex_lock(&sp->mutex); do { + pvr2_stream_internal_flush(sp); + pvr2_stream_buffer_count(sp, 0); + } while (0); mutex_unlock(&sp->mutex); +} + +static void buffer_complete(struct urb *urb) +{ + struct pvr2_buffer *bp = urb->context; + struct pvr2_stream *sp; + unsigned long irq_flags; + BUFFER_CHECK(bp); + sp = bp->stream; + bp->used_count = 0; + bp->status = 0; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferComplete %p stat=%d cnt=%d", + bp, urb->status, urb->actual_length); + spin_lock_irqsave(&sp->list_lock, irq_flags); + if ((!(urb->status)) || + (urb->status == -ENOENT) || + (urb->status == -ECONNRESET) || + (urb->status == -ESHUTDOWN)) { + (sp->buffers_processed)++; + sp->bytes_processed += urb->actual_length; + bp->used_count = urb->actual_length; + if (sp->fail_count) { + pvr2_trace(PVR2_TRACE_TOLERANCE, + "stream %p transfer ok - fail count reset", + sp); + sp->fail_count = 0; + } + } else if (sp->fail_count < sp->fail_tolerance) { + // We can tolerate this error, because we're below the + // threshold... + (sp->fail_count)++; + (sp->buffers_failed)++; + pvr2_trace(PVR2_TRACE_TOLERANCE, + "stream %p ignoring error %d - fail count increased to %u", + sp, urb->status, sp->fail_count); + } else { + (sp->buffers_failed)++; + bp->status = urb->status; + } + spin_unlock_irqrestore(&sp->list_lock, irq_flags); + pvr2_buffer_set_ready(bp); + if (sp->callback_func) { + sp->callback_func(sp->callback_data); + } +} + +struct pvr2_stream *pvr2_stream_create(void) +{ + struct pvr2_stream *sp; + sp = kzalloc(sizeof(*sp), GFP_KERNEL); + if (!sp) return sp; + pvr2_trace(PVR2_TRACE_INIT, "pvr2_stream_create: sp=%p", sp); + pvr2_stream_init(sp); + return sp; +} + +void pvr2_stream_destroy(struct pvr2_stream *sp) +{ + if (!sp) return; + pvr2_trace(PVR2_TRACE_INIT, "pvr2_stream_destroy: sp=%p", sp); + pvr2_stream_done(sp); + kfree(sp); +} + +void pvr2_stream_setup(struct pvr2_stream *sp, + struct usb_device *dev, + int endpoint, + unsigned int tolerance) +{ + mutex_lock(&sp->mutex); do { + pvr2_stream_internal_flush(sp); + sp->dev = dev; + sp->endpoint = endpoint; + sp->fail_tolerance = tolerance; + } while (0); mutex_unlock(&sp->mutex); +} + +void pvr2_stream_set_callback(struct pvr2_stream *sp, + pvr2_stream_callback func, + void *data) +{ + unsigned long irq_flags; + mutex_lock(&sp->mutex); + do { + spin_lock_irqsave(&sp->list_lock, irq_flags); + sp->callback_data = data; + sp->callback_func = func; + spin_unlock_irqrestore(&sp->list_lock, irq_flags); + } while (0); + mutex_unlock(&sp->mutex); +} + +void pvr2_stream_get_stats(struct pvr2_stream *sp, + struct pvr2_stream_stats *stats, + int zero_counts) +{ + unsigned long irq_flags; + spin_lock_irqsave(&sp->list_lock, irq_flags); + if (stats) { + stats->buffers_in_queue = sp->q_count; + stats->buffers_in_idle = sp->i_count; + stats->buffers_in_ready = sp->r_count; + stats->buffers_processed = sp->buffers_processed; + stats->buffers_failed = sp->buffers_failed; + stats->bytes_processed = sp->bytes_processed; + } + if (zero_counts) { + sp->buffers_processed = 0; + sp->buffers_failed = 0; + sp->bytes_processed = 0; + } + spin_unlock_irqrestore(&sp->list_lock, irq_flags); +} + +/* Query / set the nominal buffer count */ +int pvr2_stream_get_buffer_count(struct pvr2_stream *sp) +{ + return sp->buffer_target_count; +} + +int pvr2_stream_set_buffer_count(struct pvr2_stream *sp, unsigned int cnt) +{ + int ret; + if (sp->buffer_target_count == cnt) return 0; + mutex_lock(&sp->mutex); + do { + sp->buffer_target_count = cnt; + ret = pvr2_stream_achieve_buffer_count(sp); + } while (0); + mutex_unlock(&sp->mutex); + return ret; +} + +struct pvr2_buffer *pvr2_stream_get_idle_buffer(struct pvr2_stream *sp) +{ + struct list_head *lp = sp->idle_list.next; + if (lp == &sp->idle_list) return NULL; + return list_entry(lp, struct pvr2_buffer, list_overhead); +} + +struct pvr2_buffer *pvr2_stream_get_ready_buffer(struct pvr2_stream *sp) +{ + struct list_head *lp = sp->ready_list.next; + if (lp == &sp->ready_list) return NULL; + return list_entry(lp, struct pvr2_buffer, list_overhead); +} + +struct pvr2_buffer *pvr2_stream_get_buffer(struct pvr2_stream *sp, int id) +{ + if (id < 0) return NULL; + if (id >= sp->buffer_total_count) return NULL; + return sp->buffers[id]; +} + +int pvr2_stream_get_ready_count(struct pvr2_stream *sp) +{ + return sp->r_count; +} + +void pvr2_stream_kill(struct pvr2_stream *sp) +{ + struct pvr2_buffer *bp; + mutex_lock(&sp->mutex); + do { + pvr2_stream_internal_flush(sp); + while ((bp = pvr2_stream_get_ready_buffer(sp)) != NULL) { + pvr2_buffer_set_idle(bp); + } + if (sp->buffer_total_count != sp->buffer_target_count) { + pvr2_stream_achieve_buffer_count(sp); + } + } while (0); + mutex_unlock(&sp->mutex); +} + +int pvr2_buffer_queue(struct pvr2_buffer *bp) +{ +#undef SEED_BUFFER +#ifdef SEED_BUFFER + unsigned int idx; + unsigned int val; +#endif + int ret = 0; + struct pvr2_stream *sp; + if (!bp) return -EINVAL; + sp = bp->stream; + mutex_lock(&sp->mutex); + do { + pvr2_buffer_wipe(bp); + if (!sp->dev) { + ret = -EIO; + break; + } + pvr2_buffer_set_queued(bp); +#ifdef SEED_BUFFER + for (idx = 0; idx < (bp->max_count) / 4; idx++) { + val = bp->id << 24; + val |= idx; + ((unsigned int *)(bp->ptr))[idx] = val; + } +#endif + bp->status = -EINPROGRESS; + usb_fill_bulk_urb(bp->purb, // struct urb *urb + sp->dev, // struct usb_device *dev + // endpoint (below) + usb_rcvbulkpipe(sp->dev, sp->endpoint), + bp->ptr, // void *transfer_buffer + bp->max_count, // int buffer_length + buffer_complete, + bp); + usb_submit_urb(bp->purb, GFP_KERNEL); + } while (0); + mutex_unlock(&sp->mutex); + return ret; +} + +int pvr2_buffer_set_buffer(struct pvr2_buffer *bp, void *ptr, unsigned int cnt) +{ + int ret = 0; + unsigned long irq_flags; + struct pvr2_stream *sp; + if (!bp) return -EINVAL; + sp = bp->stream; + mutex_lock(&sp->mutex); + do { + spin_lock_irqsave(&sp->list_lock, irq_flags); + if (bp->state != pvr2_buffer_state_idle) { + ret = -EPERM; + } else { + bp->ptr = ptr; + bp->stream->i_bcount -= bp->max_count; + bp->max_count = cnt; + bp->stream->i_bcount += bp->max_count; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferPool %8s cap cap=%07d cnt=%02d", + pvr2_buffer_state_decode( + pvr2_buffer_state_idle), + bp->stream->i_bcount, bp->stream->i_count); + } + spin_unlock_irqrestore(&sp->list_lock, irq_flags); + } while (0); + mutex_unlock(&sp->mutex); + return ret; +} + +unsigned int pvr2_buffer_get_count(struct pvr2_buffer *bp) +{ + return bp->used_count; +} + +int pvr2_buffer_get_status(struct pvr2_buffer *bp) +{ + return bp->status; +} + +int pvr2_buffer_get_id(struct pvr2_buffer *bp) +{ + return bp->id; +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-io.h b/drivers/media/usb/pvrusb2/pvrusb2-io.h new file mode 100644 index 0000000000..80e4f831f5 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-io.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_IO_H +#define __PVRUSB2_IO_H + +#include <linux/usb.h> +#include <linux/list.h> + +typedef void (*pvr2_stream_callback)(void *); + +enum pvr2_buffer_state { + pvr2_buffer_state_none = 0, // Not on any list + pvr2_buffer_state_idle = 1, // Buffer is ready to be used again + pvr2_buffer_state_queued = 2, // Buffer has been queued for filling + pvr2_buffer_state_ready = 3, // Buffer has data available +}; + +struct pvr2_stream; +struct pvr2_buffer; + +struct pvr2_stream_stats { + unsigned int buffers_in_queue; + unsigned int buffers_in_idle; + unsigned int buffers_in_ready; + unsigned int buffers_processed; + unsigned int buffers_failed; + unsigned int bytes_processed; +}; + +/* Initialize / tear down stream structure */ +struct pvr2_stream *pvr2_stream_create(void); +void pvr2_stream_destroy(struct pvr2_stream *); +void pvr2_stream_setup(struct pvr2_stream *, + struct usb_device *dev,int endpoint, + unsigned int tolerance); +void pvr2_stream_set_callback(struct pvr2_stream *, + pvr2_stream_callback func, + void *data); +void pvr2_stream_get_stats(struct pvr2_stream *, + struct pvr2_stream_stats *, + int zero_counts); + +/* Query / set the nominal buffer count */ +int pvr2_stream_get_buffer_count(struct pvr2_stream *); +int pvr2_stream_set_buffer_count(struct pvr2_stream *,unsigned int); + +/* Get a pointer to a buffer that is either idle, ready, or is specified + named. */ +struct pvr2_buffer *pvr2_stream_get_idle_buffer(struct pvr2_stream *); +struct pvr2_buffer *pvr2_stream_get_ready_buffer(struct pvr2_stream *); +struct pvr2_buffer *pvr2_stream_get_buffer(struct pvr2_stream *sp,int id); + +/* Find out how many buffers are idle or ready */ +int pvr2_stream_get_ready_count(struct pvr2_stream *); + + +/* Kill all pending buffers and throw away any ready buffers as well */ +void pvr2_stream_kill(struct pvr2_stream *); + +/* Set up the actual storage for a buffer */ +int pvr2_buffer_set_buffer(struct pvr2_buffer *,void *ptr,unsigned int cnt); + +/* Find out size of data in the given ready buffer */ +unsigned int pvr2_buffer_get_count(struct pvr2_buffer *); + +/* Retrieve completion code for given ready buffer */ +int pvr2_buffer_get_status(struct pvr2_buffer *); + +/* Retrieve ID of given buffer */ +int pvr2_buffer_get_id(struct pvr2_buffer *); + +/* Start reading into given buffer (kill it if needed) */ +int pvr2_buffer_queue(struct pvr2_buffer *); + +#endif /* __PVRUSB2_IO_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-ioread.c b/drivers/media/usb/pvrusb2/pvrusb2-ioread.c new file mode 100644 index 0000000000..46f8013849 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-ioread.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ + +#include "pvrusb2-ioread.h" +#include "pvrusb2-debug.h" +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> + +#define BUFFER_COUNT 32 +#define BUFFER_SIZE PAGE_ALIGN(0x4000) + +struct pvr2_ioread { + struct pvr2_stream *stream; + char *buffer_storage[BUFFER_COUNT]; + char *sync_key_ptr; + unsigned int sync_key_len; + unsigned int sync_buf_offs; + unsigned int sync_state; + unsigned int sync_trashed_count; + int enabled; // Streaming is on + int spigot_open; // OK to pass data to client + int stream_running; // Passing data to client now + + /* State relevant to current buffer being read */ + struct pvr2_buffer *c_buf; + char *c_data_ptr; + unsigned int c_data_len; + unsigned int c_data_offs; + struct mutex mutex; +}; + +static int pvr2_ioread_init(struct pvr2_ioread *cp) +{ + unsigned int idx; + + cp->stream = NULL; + mutex_init(&cp->mutex); + + for (idx = 0; idx < BUFFER_COUNT; idx++) { + cp->buffer_storage[idx] = kmalloc(BUFFER_SIZE,GFP_KERNEL); + if (!(cp->buffer_storage[idx])) break; + } + + if (idx < BUFFER_COUNT) { + // An allocation appears to have failed + for (idx = 0; idx < BUFFER_COUNT; idx++) { + if (!(cp->buffer_storage[idx])) continue; + kfree(cp->buffer_storage[idx]); + } + return -ENOMEM; + } + return 0; +} + +static void pvr2_ioread_done(struct pvr2_ioread *cp) +{ + unsigned int idx; + + pvr2_ioread_setup(cp,NULL); + for (idx = 0; idx < BUFFER_COUNT; idx++) { + if (!(cp->buffer_storage[idx])) continue; + kfree(cp->buffer_storage[idx]); + } +} + +struct pvr2_ioread *pvr2_ioread_create(void) +{ + struct pvr2_ioread *cp; + cp = kzalloc(sizeof(*cp),GFP_KERNEL); + if (!cp) return NULL; + pvr2_trace(PVR2_TRACE_STRUCT,"pvr2_ioread_create id=%p",cp); + if (pvr2_ioread_init(cp) < 0) { + kfree(cp); + return NULL; + } + return cp; +} + +void pvr2_ioread_destroy(struct pvr2_ioread *cp) +{ + if (!cp) return; + pvr2_ioread_done(cp); + pvr2_trace(PVR2_TRACE_STRUCT,"pvr2_ioread_destroy id=%p",cp); + if (cp->sync_key_ptr) { + kfree(cp->sync_key_ptr); + cp->sync_key_ptr = NULL; + } + kfree(cp); +} + +void pvr2_ioread_set_sync_key(struct pvr2_ioread *cp, + const char *sync_key_ptr, + unsigned int sync_key_len) +{ + if (!cp) return; + + if (!sync_key_ptr) sync_key_len = 0; + if ((sync_key_len == cp->sync_key_len) && + ((!sync_key_len) || + (!memcmp(sync_key_ptr,cp->sync_key_ptr,sync_key_len)))) return; + + if (sync_key_len != cp->sync_key_len) { + if (cp->sync_key_ptr) { + kfree(cp->sync_key_ptr); + cp->sync_key_ptr = NULL; + } + cp->sync_key_len = 0; + if (sync_key_len) { + cp->sync_key_ptr = kmalloc(sync_key_len,GFP_KERNEL); + if (cp->sync_key_ptr) { + cp->sync_key_len = sync_key_len; + } + } + } + if (!cp->sync_key_len) return; + memcpy(cp->sync_key_ptr,sync_key_ptr,cp->sync_key_len); +} + +static void pvr2_ioread_stop(struct pvr2_ioread *cp) +{ + if (!(cp->enabled)) return; + pvr2_trace(PVR2_TRACE_START_STOP, + "/*---TRACE_READ---*/ pvr2_ioread_stop id=%p",cp); + pvr2_stream_kill(cp->stream); + cp->c_buf = NULL; + cp->c_data_ptr = NULL; + cp->c_data_len = 0; + cp->c_data_offs = 0; + cp->enabled = 0; + cp->stream_running = 0; + cp->spigot_open = 0; + if (cp->sync_state) { + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ sync_state <== 0"); + cp->sync_state = 0; + } +} + +static int pvr2_ioread_start(struct pvr2_ioread *cp) +{ + int stat; + struct pvr2_buffer *bp; + if (cp->enabled) return 0; + if (!(cp->stream)) return 0; + pvr2_trace(PVR2_TRACE_START_STOP, + "/*---TRACE_READ---*/ pvr2_ioread_start id=%p",cp); + while ((bp = pvr2_stream_get_idle_buffer(cp->stream)) != NULL) { + stat = pvr2_buffer_queue(bp); + if (stat < 0) { + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ pvr2_ioread_start id=%p error=%d", + cp,stat); + pvr2_ioread_stop(cp); + return stat; + } + } + cp->enabled = !0; + cp->c_buf = NULL; + cp->c_data_ptr = NULL; + cp->c_data_len = 0; + cp->c_data_offs = 0; + cp->stream_running = 0; + if (cp->sync_key_len) { + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ sync_state <== 1"); + cp->sync_state = 1; + cp->sync_trashed_count = 0; + cp->sync_buf_offs = 0; + } + cp->spigot_open = 0; + return 0; +} + +struct pvr2_stream *pvr2_ioread_get_stream(struct pvr2_ioread *cp) +{ + return cp->stream; +} + +int pvr2_ioread_setup(struct pvr2_ioread *cp,struct pvr2_stream *sp) +{ + int ret; + unsigned int idx; + struct pvr2_buffer *bp; + + mutex_lock(&cp->mutex); + do { + if (cp->stream) { + pvr2_trace(PVR2_TRACE_START_STOP, + "/*---TRACE_READ---*/ pvr2_ioread_setup (tear-down) id=%p", + cp); + pvr2_ioread_stop(cp); + pvr2_stream_kill(cp->stream); + if (pvr2_stream_get_buffer_count(cp->stream)) { + pvr2_stream_set_buffer_count(cp->stream,0); + } + cp->stream = NULL; + } + if (sp) { + pvr2_trace(PVR2_TRACE_START_STOP, + "/*---TRACE_READ---*/ pvr2_ioread_setup (setup) id=%p", + cp); + pvr2_stream_kill(sp); + ret = pvr2_stream_set_buffer_count(sp,BUFFER_COUNT); + if (ret < 0) { + mutex_unlock(&cp->mutex); + return ret; + } + for (idx = 0; idx < BUFFER_COUNT; idx++) { + bp = pvr2_stream_get_buffer(sp,idx); + pvr2_buffer_set_buffer(bp, + cp->buffer_storage[idx], + BUFFER_SIZE); + } + cp->stream = sp; + } + } while (0); + mutex_unlock(&cp->mutex); + + return 0; +} + +int pvr2_ioread_set_enabled(struct pvr2_ioread *cp,int fl) +{ + int ret = 0; + if ((!fl) == (!(cp->enabled))) return ret; + + mutex_lock(&cp->mutex); + do { + if (fl) { + ret = pvr2_ioread_start(cp); + } else { + pvr2_ioread_stop(cp); + } + } while (0); + mutex_unlock(&cp->mutex); + return ret; +} + +static int pvr2_ioread_get_buffer(struct pvr2_ioread *cp) +{ + int stat; + + while (cp->c_data_len <= cp->c_data_offs) { + if (cp->c_buf) { + // Flush out current buffer first. + stat = pvr2_buffer_queue(cp->c_buf); + if (stat < 0) { + // Streaming error... + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ pvr2_ioread_read id=%p queue_error=%d", + cp,stat); + pvr2_ioread_stop(cp); + return 0; + } + cp->c_buf = NULL; + cp->c_data_ptr = NULL; + cp->c_data_len = 0; + cp->c_data_offs = 0; + } + // Now get a freshly filled buffer. + cp->c_buf = pvr2_stream_get_ready_buffer(cp->stream); + if (!cp->c_buf) break; // Nothing ready; done. + cp->c_data_len = pvr2_buffer_get_count(cp->c_buf); + if (!cp->c_data_len) { + // Nothing transferred. Was there an error? + stat = pvr2_buffer_get_status(cp->c_buf); + if (stat < 0) { + // Streaming error... + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ pvr2_ioread_read id=%p buffer_error=%d", + cp,stat); + pvr2_ioread_stop(cp); + // Give up. + return 0; + } + // Start over... + continue; + } + cp->c_data_offs = 0; + cp->c_data_ptr = cp->buffer_storage[ + pvr2_buffer_get_id(cp->c_buf)]; + } + return !0; +} + +static void pvr2_ioread_filter(struct pvr2_ioread *cp) +{ + unsigned int idx; + if (!cp->enabled) return; + if (cp->sync_state != 1) return; + + // Search the stream for our synchronization key. This is made + // complicated by the fact that in order to be honest with + // ourselves here we must search across buffer boundaries... + mutex_lock(&cp->mutex); + while (1) { + // Ensure we have a buffer + if (!pvr2_ioread_get_buffer(cp)) break; + if (!cp->c_data_len) break; + + // Now walk the buffer contents until we match the key or + // run out of buffer data. + for (idx = cp->c_data_offs; idx < cp->c_data_len; idx++) { + if (cp->sync_buf_offs >= cp->sync_key_len) break; + if (cp->c_data_ptr[idx] == + cp->sync_key_ptr[cp->sync_buf_offs]) { + // Found the next key byte + (cp->sync_buf_offs)++; + } else { + // Whoops, mismatched. Start key over... + cp->sync_buf_offs = 0; + } + } + + // Consume what we've walked through + cp->c_data_offs += idx; + cp->sync_trashed_count += idx; + + // If we've found the key, then update state and get out. + if (cp->sync_buf_offs >= cp->sync_key_len) { + cp->sync_trashed_count -= cp->sync_key_len; + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ sync_state <== 2 (skipped %u bytes)", + cp->sync_trashed_count); + cp->sync_state = 2; + cp->sync_buf_offs = 0; + break; + } + + if (cp->c_data_offs < cp->c_data_len) { + // Sanity check - should NEVER get here + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "ERROR: pvr2_ioread filter sync problem len=%u offs=%u", + cp->c_data_len,cp->c_data_offs); + // Get out so we don't get stuck in an infinite + // loop. + break; + } + + continue; // (for clarity) + } + mutex_unlock(&cp->mutex); +} + +int pvr2_ioread_avail(struct pvr2_ioread *cp) +{ + int ret; + if (!(cp->enabled)) { + // Stream is not enabled; so this is an I/O error + return -EIO; + } + + if (cp->sync_state == 1) { + pvr2_ioread_filter(cp); + if (cp->sync_state == 1) return -EAGAIN; + } + + ret = 0; + if (cp->stream_running) { + if (!pvr2_stream_get_ready_count(cp->stream)) { + // No data available at all right now. + ret = -EAGAIN; + } + } else { + if (pvr2_stream_get_ready_count(cp->stream) < BUFFER_COUNT/2) { + // Haven't buffered up enough yet; try again later + ret = -EAGAIN; + } + } + + if ((!(cp->spigot_open)) != (!(ret == 0))) { + cp->spigot_open = (ret == 0); + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ data is %s", + cp->spigot_open ? "available" : "pending"); + } + + return ret; +} + +int pvr2_ioread_read(struct pvr2_ioread *cp,void __user *buf,unsigned int cnt) +{ + unsigned int copied_cnt; + unsigned int bcnt; + const char *src; + int stat; + int ret = 0; + unsigned int req_cnt = cnt; + + if (!cnt) { + pvr2_trace(PVR2_TRACE_TRAP, + "/*---TRACE_READ---*/ pvr2_ioread_read id=%p ZERO Request? Returning zero.", +cp); + return 0; + } + + stat = pvr2_ioread_avail(cp); + if (stat < 0) return stat; + + cp->stream_running = !0; + + mutex_lock(&cp->mutex); + do { + + // Suck data out of the buffers and copy to the user + copied_cnt = 0; + if (!buf) cnt = 0; + while (1) { + if (!pvr2_ioread_get_buffer(cp)) { + ret = -EIO; + break; + } + + if (!cnt) break; + + if (cp->sync_state == 2) { + // We're repeating the sync key data into + // the stream. + src = cp->sync_key_ptr + cp->sync_buf_offs; + bcnt = cp->sync_key_len - cp->sync_buf_offs; + } else { + // Normal buffer copy + src = cp->c_data_ptr + cp->c_data_offs; + bcnt = cp->c_data_len - cp->c_data_offs; + } + + if (!bcnt) break; + + // Don't run past user's buffer + if (bcnt > cnt) bcnt = cnt; + + if (copy_to_user(buf,src,bcnt)) { + // User supplied a bad pointer? + // Give up - this *will* cause data + // to be lost. + ret = -EFAULT; + break; + } + cnt -= bcnt; + buf += bcnt; + copied_cnt += bcnt; + + if (cp->sync_state == 2) { + // Update offset inside sync key that we're + // repeating back out. + cp->sync_buf_offs += bcnt; + if (cp->sync_buf_offs >= cp->sync_key_len) { + // Consumed entire key; switch mode + // to normal. + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ sync_state <== 0"); + cp->sync_state = 0; + } + } else { + // Update buffer offset. + cp->c_data_offs += bcnt; + } + } + + } while (0); + mutex_unlock(&cp->mutex); + + if (!ret) { + if (copied_cnt) { + // If anything was copied, return that count + ret = copied_cnt; + } else { + // Nothing copied; suggest to caller that another + // attempt should be tried again later + ret = -EAGAIN; + } + } + + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ pvr2_ioread_read id=%p request=%d result=%d", + cp,req_cnt,ret); + return ret; +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-ioread.h b/drivers/media/usb/pvrusb2/pvrusb2-ioread.h new file mode 100644 index 0000000000..33df00a7e5 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-ioread.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_IOREAD_H +#define __PVRUSB2_IOREAD_H + +#include "pvrusb2-io.h" + +struct pvr2_ioread; + +struct pvr2_ioread *pvr2_ioread_create(void); +void pvr2_ioread_destroy(struct pvr2_ioread *); +int pvr2_ioread_setup(struct pvr2_ioread *,struct pvr2_stream *); +struct pvr2_stream *pvr2_ioread_get_stream(struct pvr2_ioread *); +void pvr2_ioread_set_sync_key(struct pvr2_ioread *, + const char *sync_key_ptr, + unsigned int sync_key_len); +int pvr2_ioread_set_enabled(struct pvr2_ioread *,int fl); +int pvr2_ioread_read(struct pvr2_ioread *,void __user *buf,unsigned int cnt); +int pvr2_ioread_avail(struct pvr2_ioread *); + +#endif /* __PVRUSB2_IOREAD_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-main.c b/drivers/media/usb/pvrusb2/pvrusb2-main.c new file mode 100644 index 0000000000..721dafd2c1 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-main.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/videodev2.h> + +#include "pvrusb2-hdw.h" +#include "pvrusb2-devattr.h" +#include "pvrusb2-context.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-v4l2.h" +#include "pvrusb2-sysfs.h" + +#define DRIVER_AUTHOR "Mike Isely <isely@pobox.com>" +#define DRIVER_DESC "Hauppauge WinTV-PVR-USB2 MPEG2 Encoder/Tuner" +#define DRIVER_VERSION "V4L in-tree version" + +#define DEFAULT_DEBUG_MASK (PVR2_TRACE_ERROR_LEGS| \ + PVR2_TRACE_INFO| \ + PVR2_TRACE_STD| \ + PVR2_TRACE_TOLERANCE| \ + PVR2_TRACE_TRAP| \ + 0) + +int pvrusb2_debug = DEFAULT_DEBUG_MASK; + +module_param_named(debug,pvrusb2_debug,int,S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(debug, "Debug trace mask"); + +static void pvr_setup_attach(struct pvr2_context *pvr) +{ + /* Create association with v4l layer */ + pvr2_v4l2_create(pvr); +#ifdef CONFIG_VIDEO_PVRUSB2_DVB + /* Create association with dvb layer */ + pvr2_dvb_create(pvr); +#endif + pvr2_sysfs_create(pvr); +} + +static int pvr_probe(struct usb_interface *intf, + const struct usb_device_id *devid) +{ + struct pvr2_context *pvr; + + /* Create underlying hardware interface */ + pvr = pvr2_context_create(intf,devid,pvr_setup_attach); + if (!pvr) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to create hdw handler"); + return -ENOMEM; + } + + pvr2_trace(PVR2_TRACE_INIT,"pvr_probe(pvr=%p)",pvr); + + usb_set_intfdata(intf, pvr); + + return 0; +} + +/* + * pvr_disconnect() + * + */ +static void pvr_disconnect(struct usb_interface *intf) +{ + struct pvr2_context *pvr = usb_get_intfdata(intf); + + pvr2_trace(PVR2_TRACE_INIT,"pvr_disconnect(pvr=%p) BEGIN",pvr); + + usb_set_intfdata (intf, NULL); + pvr2_context_disconnect(pvr); + + pvr2_trace(PVR2_TRACE_INIT,"pvr_disconnect(pvr=%p) DONE",pvr); + +} + +static struct usb_driver pvr_driver = { + .name = "pvrusb2", + .id_table = pvr2_device_table, + .probe = pvr_probe, + .disconnect = pvr_disconnect +}; + +/* + * pvr_init() / pvr_exit() + * + * This code is run to initialize/exit the driver. + * + */ +static int __init pvr_init(void) +{ + int ret; + + pvr2_trace(PVR2_TRACE_INIT,"pvr_init"); + + ret = pvr2_context_global_init(); + if (ret != 0) { + pvr2_trace(PVR2_TRACE_INIT,"pvr_init failure code=%d",ret); + return ret; + } + + pvr2_sysfs_class_create(); + + ret = usb_register(&pvr_driver); + + if (ret == 0) + pr_info("pvrusb2: " DRIVER_VERSION ":" + DRIVER_DESC "\n"); + if (pvrusb2_debug) + pr_info("pvrusb2: Debug mask is %d (0x%x)\n", + pvrusb2_debug,pvrusb2_debug); + + pvr2_trace(PVR2_TRACE_INIT,"pvr_init complete"); + + return ret; +} + +static void __exit pvr_exit(void) +{ + pvr2_trace(PVR2_TRACE_INIT,"pvr_exit"); + + usb_deregister(&pvr_driver); + + pvr2_context_global_done(); + + pvr2_sysfs_class_destroy(); + + pvr2_trace(PVR2_TRACE_INIT,"pvr_exit complete"); +} + +module_init(pvr_init); +module_exit(pvr_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.9.1"); diff --git a/drivers/media/usb/pvrusb2/pvrusb2-std.c b/drivers/media/usb/pvrusb2/pvrusb2-std.c new file mode 100644 index 0000000000..e7ab414015 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-std.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ + +#include "pvrusb2-std.h" +#include "pvrusb2-debug.h" +#include <asm/string.h> +#include <linux/slab.h> + +struct std_name { + const char *name; + v4l2_std_id id; +}; + + +#define CSTD_PAL \ + (V4L2_STD_PAL_B| \ + V4L2_STD_PAL_B1| \ + V4L2_STD_PAL_G| \ + V4L2_STD_PAL_H| \ + V4L2_STD_PAL_I| \ + V4L2_STD_PAL_D| \ + V4L2_STD_PAL_D1| \ + V4L2_STD_PAL_K| \ + V4L2_STD_PAL_M| \ + V4L2_STD_PAL_N| \ + V4L2_STD_PAL_Nc| \ + V4L2_STD_PAL_60) + +#define CSTD_NTSC \ + (V4L2_STD_NTSC_M| \ + V4L2_STD_NTSC_M_JP| \ + V4L2_STD_NTSC_M_KR| \ + V4L2_STD_NTSC_443) + +#define CSTD_ATSC \ + (V4L2_STD_ATSC_8_VSB| \ + V4L2_STD_ATSC_16_VSB) + +#define CSTD_SECAM \ + (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) + +#define TSTD_B (V4L2_STD_PAL_B|V4L2_STD_SECAM_B) +#define TSTD_B1 (V4L2_STD_PAL_B1) +#define TSTD_D (V4L2_STD_PAL_D|V4L2_STD_SECAM_D) +#define TSTD_D1 (V4L2_STD_PAL_D1) +#define TSTD_G (V4L2_STD_PAL_G|V4L2_STD_SECAM_G) +#define TSTD_H (V4L2_STD_PAL_H|V4L2_STD_SECAM_H) +#define TSTD_I (V4L2_STD_PAL_I) +#define TSTD_K (V4L2_STD_PAL_K|V4L2_STD_SECAM_K) +#define TSTD_K1 (V4L2_STD_SECAM_K1) +#define TSTD_L (V4L2_STD_SECAM_L) +#define TSTD_M (V4L2_STD_PAL_M|V4L2_STD_NTSC_M) +#define TSTD_N (V4L2_STD_PAL_N) +#define TSTD_Nc (V4L2_STD_PAL_Nc) +#define TSTD_60 (V4L2_STD_PAL_60) + +#define CSTD_ALL (CSTD_PAL|CSTD_NTSC|CSTD_ATSC|CSTD_SECAM) + +/* Mapping of standard bits to color system */ +static const struct std_name std_groups[] = { + {"PAL",CSTD_PAL}, + {"NTSC",CSTD_NTSC}, + {"SECAM",CSTD_SECAM}, + {"ATSC",CSTD_ATSC}, +}; + +/* Mapping of standard bits to modulation system */ +static const struct std_name std_items[] = { + {"B",TSTD_B}, + {"B1",TSTD_B1}, + {"D",TSTD_D}, + {"D1",TSTD_D1}, + {"G",TSTD_G}, + {"H",TSTD_H}, + {"I",TSTD_I}, + {"K",TSTD_K}, + {"K1",TSTD_K1}, + {"L",TSTD_L}, + {"LC",V4L2_STD_SECAM_LC}, + {"M",TSTD_M}, + {"Mj",V4L2_STD_NTSC_M_JP}, + {"443",V4L2_STD_NTSC_443}, + {"Mk",V4L2_STD_NTSC_M_KR}, + {"N",TSTD_N}, + {"Nc",TSTD_Nc}, + {"60",TSTD_60}, + {"8VSB",V4L2_STD_ATSC_8_VSB}, + {"16VSB",V4L2_STD_ATSC_16_VSB}, +}; + + +// Search an array of std_name structures and return a pointer to the +// element with the matching name. +static const struct std_name *find_std_name(const struct std_name *arrPtr, + unsigned int arrSize, + const char *bufPtr, + unsigned int bufSize) +{ + unsigned int idx; + const struct std_name *p; + for (idx = 0; idx < arrSize; idx++) { + p = arrPtr + idx; + if (strlen(p->name) != bufSize) continue; + if (!memcmp(bufPtr,p->name,bufSize)) return p; + } + return NULL; +} + + +int pvr2_std_str_to_id(v4l2_std_id *idPtr,const char *bufPtr, + unsigned int bufSize) +{ + v4l2_std_id id = 0; + v4l2_std_id cmsk = 0; + v4l2_std_id t; + int mMode = 0; + unsigned int cnt; + char ch; + const struct std_name *sp; + + while (bufSize) { + if (!mMode) { + cnt = 0; + while ((cnt < bufSize) && (bufPtr[cnt] != '-')) cnt++; + if (cnt >= bufSize) return 0; // No more characters + sp = find_std_name(std_groups, ARRAY_SIZE(std_groups), + bufPtr,cnt); + if (!sp) return 0; // Illegal color system name + cnt++; + bufPtr += cnt; + bufSize -= cnt; + mMode = !0; + cmsk = sp->id; + continue; + } + cnt = 0; + while (cnt < bufSize) { + ch = bufPtr[cnt]; + if (ch == ';') { + mMode = 0; + break; + } + if (ch == '/') break; + cnt++; + } + sp = find_std_name(std_items, ARRAY_SIZE(std_items), + bufPtr,cnt); + if (!sp) return 0; // Illegal modulation system ID + t = sp->id & cmsk; + if (!t) return 0; // Specific color + modulation system illegal + id |= t; + if (cnt < bufSize) cnt++; + bufPtr += cnt; + bufSize -= cnt; + } + + if (idPtr) *idPtr = id; + return !0; +} + + +unsigned int pvr2_std_id_to_str(char *bufPtr, unsigned int bufSize, + v4l2_std_id id) +{ + unsigned int idx1,idx2; + const struct std_name *ip,*gp; + int gfl,cfl; + unsigned int c1,c2; + cfl = 0; + c1 = 0; + for (idx1 = 0; idx1 < ARRAY_SIZE(std_groups); idx1++) { + gp = std_groups + idx1; + gfl = 0; + for (idx2 = 0; idx2 < ARRAY_SIZE(std_items); idx2++) { + ip = std_items + idx2; + if (!(gp->id & ip->id & id)) continue; + if (!gfl) { + if (cfl) { + c2 = scnprintf(bufPtr,bufSize,";"); + c1 += c2; + bufSize -= c2; + bufPtr += c2; + } + cfl = !0; + c2 = scnprintf(bufPtr,bufSize, + "%s-",gp->name); + gfl = !0; + } else { + c2 = scnprintf(bufPtr,bufSize,"/"); + } + c1 += c2; + bufSize -= c2; + bufPtr += c2; + c2 = scnprintf(bufPtr,bufSize, + ip->name); + c1 += c2; + bufSize -= c2; + bufPtr += c2; + } + } + return c1; +} + + +// Template data for possible enumerated video standards. Here we group +// standards which share common frame rates and resolution. +static struct v4l2_standard generic_standards[] = { + { + .id = (TSTD_B|TSTD_B1| + TSTD_D|TSTD_D1| + TSTD_G| + TSTD_H| + TSTD_I| + TSTD_K|TSTD_K1| + TSTD_L| + V4L2_STD_SECAM_LC | + TSTD_N|TSTD_Nc), + .frameperiod = + { + .numerator = 1, + .denominator= 25 + }, + .framelines = 625, + .reserved = {0,0,0,0} + }, { + .id = (TSTD_M| + V4L2_STD_NTSC_M_JP| + V4L2_STD_NTSC_M_KR), + .frameperiod = + { + .numerator = 1001, + .denominator= 30000 + }, + .framelines = 525, + .reserved = {0,0,0,0} + }, { // This is a total wild guess + .id = (TSTD_60), + .frameperiod = + { + .numerator = 1001, + .denominator= 30000 + }, + .framelines = 525, + .reserved = {0,0,0,0} + }, { // This is total wild guess + .id = V4L2_STD_NTSC_443, + .frameperiod = + { + .numerator = 1001, + .denominator= 30000 + }, + .framelines = 525, + .reserved = {0,0,0,0} + } +}; + +static struct v4l2_standard *match_std(v4l2_std_id id) +{ + unsigned int idx; + for (idx = 0; idx < ARRAY_SIZE(generic_standards); idx++) { + if (generic_standards[idx].id & id) { + return generic_standards + idx; + } + } + return NULL; +} + +static int pvr2_std_fill(struct v4l2_standard *std,v4l2_std_id id) +{ + struct v4l2_standard *template; + int idx; + unsigned int bcnt; + template = match_std(id); + if (!template) return 0; + idx = std->index; + memcpy(std,template,sizeof(*template)); + std->index = idx; + std->id = id; + bcnt = pvr2_std_id_to_str(std->name,sizeof(std->name)-1,id); + std->name[bcnt] = 0; + pvr2_trace(PVR2_TRACE_STD,"Set up standard idx=%u name=%s", + std->index,std->name); + return !0; +} + +/* These are special cases of combined standards that we should enumerate + separately if the component pieces are present. */ +static v4l2_std_id std_mixes[] = { + V4L2_STD_PAL_B | V4L2_STD_PAL_G, + V4L2_STD_PAL_D | V4L2_STD_PAL_K, + V4L2_STD_SECAM_B | V4L2_STD_SECAM_G, + V4L2_STD_SECAM_D | V4L2_STD_SECAM_K, +}; + +struct v4l2_standard *pvr2_std_create_enum(unsigned int *countptr, + v4l2_std_id id) +{ + unsigned int std_cnt = 0; + unsigned int idx,bcnt,idx2; + v4l2_std_id idmsk,cmsk,fmsk; + struct v4l2_standard *stddefs; + + if (pvrusb2_debug & PVR2_TRACE_STD) { + char buf[100]; + bcnt = pvr2_std_id_to_str(buf,sizeof(buf),id); + pvr2_trace( + PVR2_TRACE_STD,"Mapping standards mask=0x%x (%.*s)", + (int)id,bcnt,buf); + } + + *countptr = 0; + std_cnt = 0; + fmsk = 0; + for (idmsk = 1, cmsk = id; cmsk; idmsk <<= 1) { + if (!(idmsk & cmsk)) continue; + cmsk &= ~idmsk; + if (match_std(idmsk)) { + std_cnt++; + continue; + } + fmsk |= idmsk; + } + + for (idx2 = 0; idx2 < ARRAY_SIZE(std_mixes); idx2++) { + if ((id & std_mixes[idx2]) == std_mixes[idx2]) std_cnt++; + } + + /* Don't complain about ATSC standard values */ + fmsk &= ~CSTD_ATSC; + + if (fmsk) { + char buf[100]; + bcnt = pvr2_std_id_to_str(buf,sizeof(buf),fmsk); + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "***WARNING*** Failed to classify the following standard(s): %.*s", + bcnt,buf); + } + + pvr2_trace(PVR2_TRACE_STD,"Setting up %u unique standard(s)", + std_cnt); + if (!std_cnt) return NULL; // paranoia + + stddefs = kcalloc(std_cnt, sizeof(struct v4l2_standard), + GFP_KERNEL); + if (!stddefs) + return NULL; + + for (idx = 0; idx < std_cnt; idx++) + stddefs[idx].index = idx; + + idx = 0; + + /* Enumerate potential special cases */ + for (idx2 = 0; (idx2 < ARRAY_SIZE(std_mixes)) && (idx < std_cnt); + idx2++) { + if (!(id & std_mixes[idx2])) continue; + if (pvr2_std_fill(stddefs+idx,std_mixes[idx2])) idx++; + } + /* Now enumerate individual pieces */ + for (idmsk = 1, cmsk = id; cmsk && (idx < std_cnt); idmsk <<= 1) { + if (!(idmsk & cmsk)) continue; + cmsk &= ~idmsk; + if (!pvr2_std_fill(stddefs+idx,idmsk)) continue; + idx++; + } + + *countptr = std_cnt; + return stddefs; +} + +v4l2_std_id pvr2_std_get_usable(void) +{ + return CSTD_ALL; +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-std.h b/drivers/media/usb/pvrusb2/pvrusb2-std.h new file mode 100644 index 0000000000..d8b4c6dc72 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-std.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_STD_H +#define __PVRUSB2_STD_H + +#include <linux/videodev2.h> + +// Convert string describing one or more video standards into a mask of V4L +// standard bits. Return true if conversion succeeds otherwise return +// false. String is expected to be of the form: C1-x/y;C2-a/b where C1 and +// C2 are color system names (e.g. "PAL", "NTSC") and x, y, a, and b are +// modulation schemes (e.g. "M", "B", "G", etc). +int pvr2_std_str_to_id(v4l2_std_id *idPtr,const char *bufPtr, + unsigned int bufSize); + +// Convert any arbitrary set of video standard bits into an unambiguous +// readable string. Return value is the number of bytes consumed in the +// buffer. The formatted string is of a form that can be parsed by our +// sibling std_std_to_id() function. +unsigned int pvr2_std_id_to_str(char *bufPtr, unsigned int bufSize, + v4l2_std_id id); + +// Create an array of suitable v4l2_standard structures given a bit mask of +// video standards to support. The array is allocated from the heap, and +// the number of elements is returned in the first argument. +struct v4l2_standard *pvr2_std_create_enum(unsigned int *countptr, + v4l2_std_id id); + +// Return mask of which video standard bits are valid +v4l2_std_id pvr2_std_get_usable(void); + +#endif /* __PVRUSB2_STD_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-sysfs.c b/drivers/media/usb/pvrusb2/pvrusb2-sysfs.c new file mode 100644 index 0000000000..3077399901 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-sysfs.c @@ -0,0 +1,811 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ + +#include <linux/string.h> +#include <linux/slab.h> +#include "pvrusb2-sysfs.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-debug.h" +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC +#include "pvrusb2-debugifc.h" +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ + +#define pvr2_sysfs_trace(...) pvr2_trace(PVR2_TRACE_SYSFS,__VA_ARGS__) + +struct pvr2_sysfs { + struct pvr2_channel channel; + struct device *class_dev; +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC + struct pvr2_sysfs_debugifc *debugifc; +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ + struct pvr2_sysfs_ctl_item *item_first; + struct pvr2_sysfs_ctl_item *item_last; + struct device_attribute attr_v4l_minor_number; + struct device_attribute attr_v4l_radio_minor_number; + struct device_attribute attr_unit_number; + struct device_attribute attr_bus_info; + struct device_attribute attr_hdw_name; + struct device_attribute attr_hdw_desc; + int v4l_minor_number_created_ok; + int v4l_radio_minor_number_created_ok; + int unit_number_created_ok; + int bus_info_created_ok; + int hdw_name_created_ok; + int hdw_desc_created_ok; +}; + +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC +struct pvr2_sysfs_debugifc { + struct device_attribute attr_debugcmd; + struct device_attribute attr_debuginfo; + int debugcmd_created_ok; + int debuginfo_created_ok; +}; +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ + +struct pvr2_sysfs_ctl_item { + struct device_attribute attr_name; + struct device_attribute attr_type; + struct device_attribute attr_min; + struct device_attribute attr_max; + struct device_attribute attr_def; + struct device_attribute attr_enum; + struct device_attribute attr_bits; + struct device_attribute attr_val; + struct device_attribute attr_custom; + struct pvr2_ctrl *cptr; + int ctl_id; + struct pvr2_sysfs *chptr; + struct pvr2_sysfs_ctl_item *item_next; + struct attribute *attr_gen[8]; + struct attribute_group grp; + int created_ok; + char name[80]; +}; + +static ssize_t show_name(struct device *class_dev, + struct device_attribute *attr, + char *buf) +{ + struct pvr2_sysfs_ctl_item *cip; + const char *name; + cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_name); + name = pvr2_ctrl_get_desc(cip->cptr); + pvr2_sysfs_trace("pvr2_sysfs(%p) show_name(cid=%d) is %s", + cip->chptr, cip->ctl_id, name); + if (!name) return -EINVAL; + return sysfs_emit(buf, "%s\n", name); +} + +static ssize_t show_type(struct device *class_dev, + struct device_attribute *attr, + char *buf) +{ + struct pvr2_sysfs_ctl_item *cip; + const char *name; + enum pvr2_ctl_type tp; + cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_type); + tp = pvr2_ctrl_get_type(cip->cptr); + switch (tp) { + case pvr2_ctl_int: name = "integer"; break; + case pvr2_ctl_enum: name = "enum"; break; + case pvr2_ctl_bitmask: name = "bitmask"; break; + case pvr2_ctl_bool: name = "boolean"; break; + default: name = "?"; break; + } + pvr2_sysfs_trace("pvr2_sysfs(%p) show_type(cid=%d) is %s", + cip->chptr, cip->ctl_id, name); + return sysfs_emit(buf, "%s\n", name); +} + +static ssize_t show_min(struct device *class_dev, + struct device_attribute *attr, + char *buf) +{ + struct pvr2_sysfs_ctl_item *cip; + long val; + cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_min); + val = pvr2_ctrl_get_min(cip->cptr); + pvr2_sysfs_trace("pvr2_sysfs(%p) show_min(cid=%d) is %ld", + cip->chptr, cip->ctl_id, val); + return sysfs_emit(buf, "%ld\n", val); +} + +static ssize_t show_max(struct device *class_dev, + struct device_attribute *attr, + char *buf) +{ + struct pvr2_sysfs_ctl_item *cip; + long val; + cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_max); + val = pvr2_ctrl_get_max(cip->cptr); + pvr2_sysfs_trace("pvr2_sysfs(%p) show_max(cid=%d) is %ld", + cip->chptr, cip->ctl_id, val); + return sysfs_emit(buf, "%ld\n", val); +} + +static ssize_t show_def(struct device *class_dev, + struct device_attribute *attr, + char *buf) +{ + struct pvr2_sysfs_ctl_item *cip; + int val; + int ret; + unsigned int cnt = 0; + cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_def); + ret = pvr2_ctrl_get_def(cip->cptr, &val); + if (ret < 0) return ret; + ret = pvr2_ctrl_value_to_sym(cip->cptr, ~0, val, + buf, PAGE_SIZE - 1, &cnt); + pvr2_sysfs_trace("pvr2_sysfs(%p) show_def(cid=%d) is %.*s (%d)", + cip->chptr, cip->ctl_id, cnt, buf, val); + buf[cnt] = '\n'; + return cnt + 1; +} + +static ssize_t show_val_norm(struct device *class_dev, + struct device_attribute *attr, + char *buf) +{ + struct pvr2_sysfs_ctl_item *cip; + int val; + int ret; + unsigned int cnt = 0; + cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_val); + ret = pvr2_ctrl_get_value(cip->cptr, &val); + if (ret < 0) return ret; + ret = pvr2_ctrl_value_to_sym(cip->cptr, ~0, val, + buf, PAGE_SIZE - 1, &cnt); + pvr2_sysfs_trace("pvr2_sysfs(%p) show_val_norm(cid=%d) is %.*s (%d)", + cip->chptr, cip->ctl_id, cnt, buf, val); + buf[cnt] = '\n'; + return cnt+1; +} + +static ssize_t show_val_custom(struct device *class_dev, + struct device_attribute *attr, + char *buf) +{ + struct pvr2_sysfs_ctl_item *cip; + int val; + int ret; + unsigned int cnt = 0; + cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_custom); + ret = pvr2_ctrl_get_value(cip->cptr, &val); + if (ret < 0) return ret; + ret = pvr2_ctrl_custom_value_to_sym(cip->cptr, ~0, val, + buf, PAGE_SIZE - 1, &cnt); + pvr2_sysfs_trace("pvr2_sysfs(%p) show_val_custom(cid=%d) is %.*s (%d)", + cip->chptr, cip->ctl_id, cnt, buf, val); + buf[cnt] = '\n'; + return cnt+1; +} + +static ssize_t show_enum(struct device *class_dev, + struct device_attribute *attr, + char *buf) +{ + struct pvr2_sysfs_ctl_item *cip; + long val; + unsigned int bcnt, ccnt, ecnt; + cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_enum); + ecnt = pvr2_ctrl_get_cnt(cip->cptr); + bcnt = 0; + for (val = 0; val < ecnt; val++) { + pvr2_ctrl_get_valname(cip->cptr, val, buf + bcnt, + PAGE_SIZE - bcnt, &ccnt); + if (!ccnt) continue; + bcnt += ccnt; + if (bcnt >= PAGE_SIZE) break; + buf[bcnt] = '\n'; + bcnt++; + } + pvr2_sysfs_trace("pvr2_sysfs(%p) show_enum(cid=%d)", + cip->chptr, cip->ctl_id); + return bcnt; +} + +static ssize_t show_bits(struct device *class_dev, + struct device_attribute *attr, + char *buf) +{ + struct pvr2_sysfs_ctl_item *cip; + int valid_bits, msk; + unsigned int bcnt, ccnt; + cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_bits); + valid_bits = pvr2_ctrl_get_mask(cip->cptr); + bcnt = 0; + for (msk = 1; valid_bits; msk <<= 1) { + if (!(msk & valid_bits)) continue; + valid_bits &= ~msk; + pvr2_ctrl_get_valname(cip->cptr, msk, buf + bcnt, + PAGE_SIZE - bcnt, &ccnt); + bcnt += ccnt; + if (bcnt >= PAGE_SIZE) break; + buf[bcnt] = '\n'; + bcnt++; + } + pvr2_sysfs_trace("pvr2_sysfs(%p) show_bits(cid=%d)", + cip->chptr, cip->ctl_id); + return bcnt; +} + +static int store_val_any(struct pvr2_sysfs_ctl_item *cip, int customfl, + const char *buf,unsigned int count) +{ + int ret; + int mask,val; + if (customfl) { + ret = pvr2_ctrl_custom_sym_to_value(cip->cptr, buf, count, + &mask, &val); + } else { + ret = pvr2_ctrl_sym_to_value(cip->cptr, buf, count, + &mask, &val); + } + if (ret < 0) return ret; + ret = pvr2_ctrl_set_mask_value(cip->cptr, mask, val); + pvr2_hdw_commit_ctl(cip->chptr->channel.hdw); + return ret; +} + +static ssize_t store_val_norm(struct device *class_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pvr2_sysfs_ctl_item *cip; + int ret; + cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_val); + pvr2_sysfs_trace("pvr2_sysfs(%p) store_val_norm(cid=%d) \"%.*s\"", + cip->chptr, cip->ctl_id, (int)count, buf); + ret = store_val_any(cip, 0, buf, count); + if (!ret) ret = count; + return ret; +} + +static ssize_t store_val_custom(struct device *class_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pvr2_sysfs_ctl_item *cip; + int ret; + cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_custom); + pvr2_sysfs_trace("pvr2_sysfs(%p) store_val_custom(cid=%d) \"%.*s\"", + cip->chptr, cip->ctl_id, (int)count, buf); + ret = store_val_any(cip, 1, buf, count); + if (!ret) ret = count; + return ret; +} + +static void pvr2_sysfs_add_control(struct pvr2_sysfs *sfp,int ctl_id) +{ + struct pvr2_sysfs_ctl_item *cip; + struct pvr2_ctrl *cptr; + unsigned int cnt,acnt; + int ret; + + cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,ctl_id); + if (!cptr) return; + + cip = kzalloc(sizeof(*cip),GFP_KERNEL); + if (!cip) return; + pvr2_sysfs_trace("Creating pvr2_sysfs_ctl_item id=%p",cip); + + cip->cptr = cptr; + cip->ctl_id = ctl_id; + + cip->chptr = sfp; + cip->item_next = NULL; + if (sfp->item_last) { + sfp->item_last->item_next = cip; + } else { + sfp->item_first = cip; + } + sfp->item_last = cip; + + sysfs_attr_init(&cip->attr_name.attr); + cip->attr_name.attr.name = "name"; + cip->attr_name.attr.mode = S_IRUGO; + cip->attr_name.show = show_name; + + sysfs_attr_init(&cip->attr_type.attr); + cip->attr_type.attr.name = "type"; + cip->attr_type.attr.mode = S_IRUGO; + cip->attr_type.show = show_type; + + sysfs_attr_init(&cip->attr_min.attr); + cip->attr_min.attr.name = "min_val"; + cip->attr_min.attr.mode = S_IRUGO; + cip->attr_min.show = show_min; + + sysfs_attr_init(&cip->attr_max.attr); + cip->attr_max.attr.name = "max_val"; + cip->attr_max.attr.mode = S_IRUGO; + cip->attr_max.show = show_max; + + sysfs_attr_init(&cip->attr_def.attr); + cip->attr_def.attr.name = "def_val"; + cip->attr_def.attr.mode = S_IRUGO; + cip->attr_def.show = show_def; + + sysfs_attr_init(&cip->attr_val.attr); + cip->attr_val.attr.name = "cur_val"; + cip->attr_val.attr.mode = S_IRUGO; + + sysfs_attr_init(&cip->attr_custom.attr); + cip->attr_custom.attr.name = "custom_val"; + cip->attr_custom.attr.mode = S_IRUGO; + + sysfs_attr_init(&cip->attr_enum.attr); + cip->attr_enum.attr.name = "enum_val"; + cip->attr_enum.attr.mode = S_IRUGO; + cip->attr_enum.show = show_enum; + + sysfs_attr_init(&cip->attr_bits.attr); + cip->attr_bits.attr.name = "bit_val"; + cip->attr_bits.attr.mode = S_IRUGO; + cip->attr_bits.show = show_bits; + + if (pvr2_ctrl_is_writable(cptr)) { + cip->attr_val.attr.mode |= S_IWUSR|S_IWGRP; + cip->attr_custom.attr.mode |= S_IWUSR|S_IWGRP; + } + + acnt = 0; + cip->attr_gen[acnt++] = &cip->attr_name.attr; + cip->attr_gen[acnt++] = &cip->attr_type.attr; + cip->attr_gen[acnt++] = &cip->attr_val.attr; + cip->attr_gen[acnt++] = &cip->attr_def.attr; + cip->attr_val.show = show_val_norm; + cip->attr_val.store = store_val_norm; + if (pvr2_ctrl_has_custom_symbols(cptr)) { + cip->attr_gen[acnt++] = &cip->attr_custom.attr; + cip->attr_custom.show = show_val_custom; + cip->attr_custom.store = store_val_custom; + } + switch (pvr2_ctrl_get_type(cptr)) { + case pvr2_ctl_enum: + // Control is an enumeration + cip->attr_gen[acnt++] = &cip->attr_enum.attr; + break; + case pvr2_ctl_int: + // Control is an integer + cip->attr_gen[acnt++] = &cip->attr_min.attr; + cip->attr_gen[acnt++] = &cip->attr_max.attr; + break; + case pvr2_ctl_bitmask: + // Control is an bitmask + cip->attr_gen[acnt++] = &cip->attr_bits.attr; + break; + default: break; + } + + cnt = scnprintf(cip->name,sizeof(cip->name)-1,"ctl_%s", + pvr2_ctrl_get_name(cptr)); + cip->name[cnt] = 0; + cip->grp.name = cip->name; + cip->grp.attrs = cip->attr_gen; + + ret = sysfs_create_group(&sfp->class_dev->kobj,&cip->grp); + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "sysfs_create_group error: %d", + ret); + return; + } + cip->created_ok = !0; +} + +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC +static ssize_t debuginfo_show(struct device *, struct device_attribute *, + char *); +static ssize_t debugcmd_show(struct device *, struct device_attribute *, + char *); +static ssize_t debugcmd_store(struct device *, struct device_attribute *, + const char *, size_t count); + +static void pvr2_sysfs_add_debugifc(struct pvr2_sysfs *sfp) +{ + struct pvr2_sysfs_debugifc *dip; + int ret; + + dip = kzalloc(sizeof(*dip),GFP_KERNEL); + if (!dip) return; + sysfs_attr_init(&dip->attr_debugcmd.attr); + dip->attr_debugcmd.attr.name = "debugcmd"; + dip->attr_debugcmd.attr.mode = S_IRUGO|S_IWUSR|S_IWGRP; + dip->attr_debugcmd.show = debugcmd_show; + dip->attr_debugcmd.store = debugcmd_store; + sysfs_attr_init(&dip->attr_debuginfo.attr); + dip->attr_debuginfo.attr.name = "debuginfo"; + dip->attr_debuginfo.attr.mode = S_IRUGO; + dip->attr_debuginfo.show = debuginfo_show; + sfp->debugifc = dip; + ret = device_create_file(sfp->class_dev,&dip->attr_debugcmd); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "device_create_file error: %d", + ret); + } else { + dip->debugcmd_created_ok = !0; + } + ret = device_create_file(sfp->class_dev,&dip->attr_debuginfo); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "device_create_file error: %d", + ret); + } else { + dip->debuginfo_created_ok = !0; + } +} + + +static void pvr2_sysfs_tear_down_debugifc(struct pvr2_sysfs *sfp) +{ + if (!sfp->debugifc) return; + if (sfp->debugifc->debuginfo_created_ok) { + device_remove_file(sfp->class_dev, + &sfp->debugifc->attr_debuginfo); + } + if (sfp->debugifc->debugcmd_created_ok) { + device_remove_file(sfp->class_dev, + &sfp->debugifc->attr_debugcmd); + } + kfree(sfp->debugifc); + sfp->debugifc = NULL; +} +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ + + +static void pvr2_sysfs_add_controls(struct pvr2_sysfs *sfp) +{ + unsigned int idx,cnt; + cnt = pvr2_hdw_get_ctrl_count(sfp->channel.hdw); + for (idx = 0; idx < cnt; idx++) { + pvr2_sysfs_add_control(sfp,idx); + } +} + + +static void pvr2_sysfs_tear_down_controls(struct pvr2_sysfs *sfp) +{ + struct pvr2_sysfs_ctl_item *cip1,*cip2; + for (cip1 = sfp->item_first; cip1; cip1 = cip2) { + cip2 = cip1->item_next; + if (cip1->created_ok) { + sysfs_remove_group(&sfp->class_dev->kobj,&cip1->grp); + } + pvr2_sysfs_trace("Destroying pvr2_sysfs_ctl_item id=%p",cip1); + kfree(cip1); + } +} + + +static void pvr2_sysfs_release(struct device *class_dev) +{ + pvr2_sysfs_trace("Releasing class_dev id=%p",class_dev); + kfree(class_dev); +} + + +static struct class pvr2_class = { + .name = "pvrusb2", + .dev_release = pvr2_sysfs_release, +}; + + +static void class_dev_destroy(struct pvr2_sysfs *sfp) +{ + struct device *dev; + if (!sfp->class_dev) return; +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC + pvr2_sysfs_tear_down_debugifc(sfp); +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ + pvr2_sysfs_tear_down_controls(sfp); + if (sfp->hdw_desc_created_ok) { + device_remove_file(sfp->class_dev, + &sfp->attr_hdw_desc); + } + if (sfp->hdw_name_created_ok) { + device_remove_file(sfp->class_dev, + &sfp->attr_hdw_name); + } + if (sfp->bus_info_created_ok) { + device_remove_file(sfp->class_dev, + &sfp->attr_bus_info); + } + if (sfp->v4l_minor_number_created_ok) { + device_remove_file(sfp->class_dev, + &sfp->attr_v4l_minor_number); + } + if (sfp->v4l_radio_minor_number_created_ok) { + device_remove_file(sfp->class_dev, + &sfp->attr_v4l_radio_minor_number); + } + if (sfp->unit_number_created_ok) { + device_remove_file(sfp->class_dev, + &sfp->attr_unit_number); + } + pvr2_sysfs_trace("Destroying class_dev id=%p",sfp->class_dev); + dev_set_drvdata(sfp->class_dev, NULL); + dev = sfp->class_dev->parent; + sfp->class_dev->parent = NULL; + put_device(dev); + device_unregister(sfp->class_dev); + sfp->class_dev = NULL; +} + + +static ssize_t v4l_minor_number_show(struct device *class_dev, + struct device_attribute *attr, char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = dev_get_drvdata(class_dev); + if (!sfp) return -EINVAL; + return sysfs_emit(buf, "%d\n", + pvr2_hdw_v4l_get_minor_number(sfp->channel.hdw, + pvr2_v4l_type_video)); +} + + +static ssize_t bus_info_show(struct device *class_dev, + struct device_attribute *attr, char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = dev_get_drvdata(class_dev); + if (!sfp) return -EINVAL; + return sysfs_emit(buf, "%s\n", + pvr2_hdw_get_bus_info(sfp->channel.hdw)); +} + + +static ssize_t hdw_name_show(struct device *class_dev, + struct device_attribute *attr, char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = dev_get_drvdata(class_dev); + if (!sfp) return -EINVAL; + return sysfs_emit(buf, "%s\n", + pvr2_hdw_get_type(sfp->channel.hdw)); +} + + +static ssize_t hdw_desc_show(struct device *class_dev, + struct device_attribute *attr, char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = dev_get_drvdata(class_dev); + if (!sfp) return -EINVAL; + return sysfs_emit(buf, "%s\n", + pvr2_hdw_get_desc(sfp->channel.hdw)); +} + + +static ssize_t v4l_radio_minor_number_show(struct device *class_dev, + struct device_attribute *attr, + char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = dev_get_drvdata(class_dev); + if (!sfp) return -EINVAL; + return sysfs_emit(buf, "%d\n", + pvr2_hdw_v4l_get_minor_number(sfp->channel.hdw, + pvr2_v4l_type_radio)); +} + + +static ssize_t unit_number_show(struct device *class_dev, + struct device_attribute *attr, char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = dev_get_drvdata(class_dev); + if (!sfp) return -EINVAL; + return sysfs_emit(buf, "%d\n", + pvr2_hdw_get_unit_number(sfp->channel.hdw)); +} + + +static void class_dev_create(struct pvr2_sysfs *sfp) +{ + struct usb_device *usb_dev; + struct device *class_dev; + int ret; + + usb_dev = pvr2_hdw_get_dev(sfp->channel.hdw); + if (!usb_dev) return; + class_dev = kzalloc(sizeof(*class_dev),GFP_KERNEL); + if (!class_dev) return; + + pvr2_sysfs_trace("Creating class_dev id=%p",class_dev); + + class_dev->class = &pvr2_class; + + dev_set_name(class_dev, "%s", + pvr2_hdw_get_device_identifier(sfp->channel.hdw)); + + class_dev->parent = get_device(&usb_dev->dev); + + sfp->class_dev = class_dev; + dev_set_drvdata(class_dev, sfp); + ret = device_register(class_dev); + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "device_register failed"); + put_device(class_dev); + return; + } + + sysfs_attr_init(&sfp->attr_v4l_minor_number.attr); + sfp->attr_v4l_minor_number.attr.name = "v4l_minor_number"; + sfp->attr_v4l_minor_number.attr.mode = S_IRUGO; + sfp->attr_v4l_minor_number.show = v4l_minor_number_show; + sfp->attr_v4l_minor_number.store = NULL; + ret = device_create_file(sfp->class_dev, + &sfp->attr_v4l_minor_number); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "device_create_file error: %d", + ret); + } else { + sfp->v4l_minor_number_created_ok = !0; + } + + sysfs_attr_init(&sfp->attr_v4l_radio_minor_number.attr); + sfp->attr_v4l_radio_minor_number.attr.name = "v4l_radio_minor_number"; + sfp->attr_v4l_radio_minor_number.attr.mode = S_IRUGO; + sfp->attr_v4l_radio_minor_number.show = v4l_radio_minor_number_show; + sfp->attr_v4l_radio_minor_number.store = NULL; + ret = device_create_file(sfp->class_dev, + &sfp->attr_v4l_radio_minor_number); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "device_create_file error: %d", + ret); + } else { + sfp->v4l_radio_minor_number_created_ok = !0; + } + + sysfs_attr_init(&sfp->attr_unit_number.attr); + sfp->attr_unit_number.attr.name = "unit_number"; + sfp->attr_unit_number.attr.mode = S_IRUGO; + sfp->attr_unit_number.show = unit_number_show; + sfp->attr_unit_number.store = NULL; + ret = device_create_file(sfp->class_dev,&sfp->attr_unit_number); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "device_create_file error: %d", + ret); + } else { + sfp->unit_number_created_ok = !0; + } + + sysfs_attr_init(&sfp->attr_bus_info.attr); + sfp->attr_bus_info.attr.name = "bus_info_str"; + sfp->attr_bus_info.attr.mode = S_IRUGO; + sfp->attr_bus_info.show = bus_info_show; + sfp->attr_bus_info.store = NULL; + ret = device_create_file(sfp->class_dev, + &sfp->attr_bus_info); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "device_create_file error: %d", + ret); + } else { + sfp->bus_info_created_ok = !0; + } + + sysfs_attr_init(&sfp->attr_hdw_name.attr); + sfp->attr_hdw_name.attr.name = "device_hardware_type"; + sfp->attr_hdw_name.attr.mode = S_IRUGO; + sfp->attr_hdw_name.show = hdw_name_show; + sfp->attr_hdw_name.store = NULL; + ret = device_create_file(sfp->class_dev, + &sfp->attr_hdw_name); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "device_create_file error: %d", + ret); + } else { + sfp->hdw_name_created_ok = !0; + } + + sysfs_attr_init(&sfp->attr_hdw_desc.attr); + sfp->attr_hdw_desc.attr.name = "device_hardware_description"; + sfp->attr_hdw_desc.attr.mode = S_IRUGO; + sfp->attr_hdw_desc.show = hdw_desc_show; + sfp->attr_hdw_desc.store = NULL; + ret = device_create_file(sfp->class_dev, + &sfp->attr_hdw_desc); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "device_create_file error: %d", + ret); + } else { + sfp->hdw_desc_created_ok = !0; + } + + pvr2_sysfs_add_controls(sfp); +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC + pvr2_sysfs_add_debugifc(sfp); +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ +} + + +static void pvr2_sysfs_internal_check(struct pvr2_channel *chp) +{ + struct pvr2_sysfs *sfp; + sfp = container_of(chp,struct pvr2_sysfs,channel); + if (!sfp->channel.mc_head->disconnect_flag) return; + pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_sysfs id=%p",sfp); + class_dev_destroy(sfp); + pvr2_channel_done(&sfp->channel); + kfree(sfp); +} + + +void pvr2_sysfs_create(struct pvr2_context *mp) +{ + struct pvr2_sysfs *sfp; + sfp = kzalloc(sizeof(*sfp),GFP_KERNEL); + if (!sfp) + return; + pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_sysfs id=%p",sfp); + pvr2_channel_init(&sfp->channel,mp); + sfp->channel.check_func = pvr2_sysfs_internal_check; + + class_dev_create(sfp); +} + + +void pvr2_sysfs_class_create(void) +{ + if (class_register(&pvr2_class)) + pvr2_sysfs_trace("Registration failed for pvr2_sysfs_class"); +} + + +void pvr2_sysfs_class_destroy(void) +{ + class_unregister(&pvr2_class); +} + + +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC +static ssize_t debuginfo_show(struct device *class_dev, + struct device_attribute *attr, char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = dev_get_drvdata(class_dev); + if (!sfp) return -EINVAL; + pvr2_hdw_trigger_module_log(sfp->channel.hdw); + return pvr2_debugifc_print_info(sfp->channel.hdw,buf,PAGE_SIZE); +} + + +static ssize_t debugcmd_show(struct device *class_dev, + struct device_attribute *attr, char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = dev_get_drvdata(class_dev); + if (!sfp) return -EINVAL; + return pvr2_debugifc_print_status(sfp->channel.hdw,buf,PAGE_SIZE); +} + + +static ssize_t debugcmd_store(struct device *class_dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pvr2_sysfs *sfp; + int ret; + + sfp = dev_get_drvdata(class_dev); + if (!sfp) return -EINVAL; + + ret = pvr2_debugifc_docmd(sfp->channel.hdw,buf,count); + if (ret < 0) return ret; + return count; +} +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-sysfs.h b/drivers/media/usb/pvrusb2/pvrusb2-sysfs.h new file mode 100644 index 0000000000..375a5372e9 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-sysfs.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_SYSFS_H +#define __PVRUSB2_SYSFS_H + +#include <linux/list.h> +#include <linux/sysfs.h> +#include "pvrusb2-context.h" + +#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS +void pvr2_sysfs_class_create(void); +void pvr2_sysfs_class_destroy(void); +void pvr2_sysfs_create(struct pvr2_context *mp); +#else +static inline void pvr2_sysfs_class_create(void) { } +static inline void pvr2_sysfs_class_destroy(void) { } +static inline void pvr2_sysfs_create(struct pvr2_context *mp) { } +#endif + + +#endif /* __PVRUSB2_SYSFS_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-util.h b/drivers/media/usb/pvrusb2/pvrusb2-util.h new file mode 100644 index 0000000000..7059772a49 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-util.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_UTIL_H +#define __PVRUSB2_UTIL_H + +#define PVR2_DECOMPOSE_LE(t,i,d) \ + do { \ + (t)[i] = (d) & 0xff;\ + (t)[i+1] = ((d) >> 8) & 0xff;\ + (t)[i+2] = ((d) >> 16) & 0xff;\ + (t)[i+3] = ((d) >> 24) & 0xff;\ + } while(0) + +#define PVR2_DECOMPOSE_BE(t,i,d) \ + do { \ + (t)[i+3] = (d) & 0xff;\ + (t)[i+2] = ((d) >> 8) & 0xff;\ + (t)[i+1] = ((d) >> 16) & 0xff;\ + (t)[i] = ((d) >> 24) & 0xff;\ + } while(0) + +#define PVR2_COMPOSE_LE(t,i) \ + ((((u32)((t)[i+3])) << 24) | \ + (((u32)((t)[i+2])) << 16) | \ + (((u32)((t)[i+1])) << 8) | \ + ((u32)((t)[i]))) + +#define PVR2_COMPOSE_BE(t,i) \ + ((((u32)((t)[i])) << 24) | \ + (((u32)((t)[i+1])) << 16) | \ + (((u32)((t)[i+2])) << 8) | \ + ((u32)((t)[i+3]))) + + +#endif /* __PVRUSB2_UTIL_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-v4l2.c b/drivers/media/usb/pvrusb2/pvrusb2-v4l2.c new file mode 100644 index 0000000000..c04ab7258d --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-v4l2.c @@ -0,0 +1,1288 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include "pvrusb2-context.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-v4l2.h" +#include "pvrusb2-ioread.h" +#include <linux/videodev2.h> +#include <linux/module.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> + +struct pvr2_v4l2_dev; +struct pvr2_v4l2_fh; +struct pvr2_v4l2; + +struct pvr2_v4l2_dev { + struct video_device devbase; /* MUST be first! */ + struct pvr2_v4l2 *v4lp; + struct pvr2_context_stream *stream; + /* Information about this device: */ + enum pvr2_config config; /* Expected stream format */ + int v4l_type; /* V4L defined type for this device node */ + enum pvr2_v4l_type minor_type; /* pvr2-understood minor device type */ +}; + +struct pvr2_v4l2_fh { + struct v4l2_fh fh; + struct pvr2_channel channel; + struct pvr2_v4l2_dev *pdi; + struct pvr2_ioread *rhp; + struct file *file; + wait_queue_head_t wait_data; + int fw_mode_flag; + /* Map contiguous ordinal value to input id */ + unsigned char *input_map; + unsigned int input_cnt; +}; + +struct pvr2_v4l2 { + struct pvr2_channel channel; + + /* streams - Note that these must be separately, individually, + * allocated pointers. This is because the v4l core is going to + * manage their deletion - separately, individually... */ + struct pvr2_v4l2_dev *dev_video; + struct pvr2_v4l2_dev *dev_radio; +}; + +static int video_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1}; +module_param_array(video_nr, int, NULL, 0444); +MODULE_PARM_DESC(video_nr, "Offset for device's video dev minor"); +static int radio_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1}; +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Offset for device's radio dev minor"); +static int vbi_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1}; +module_param_array(vbi_nr, int, NULL, 0444); +MODULE_PARM_DESC(vbi_nr, "Offset for device's vbi dev minor"); + +#define PVR_FORMAT_PIX 0 +#define PVR_FORMAT_VBI 1 + +static struct v4l2_format pvr_format [] = { + [PVR_FORMAT_PIX] = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .fmt = { + .pix = { + .width = 720, + .height = 576, + .pixelformat = V4L2_PIX_FMT_MPEG, + .field = V4L2_FIELD_INTERLACED, + /* FIXME : Don't know what to put here... */ + .sizeimage = 32 * 1024, + } + } + }, + [PVR_FORMAT_VBI] = { + .type = V4L2_BUF_TYPE_VBI_CAPTURE, + .fmt = { + .vbi = { + .sampling_rate = 27000000, + .offset = 248, + .samples_per_line = 1443, + .sample_format = V4L2_PIX_FMT_GREY, + .start = { 0, 0 }, + .count = { 0, 0 }, + .flags = 0, + } + } + } +}; + + + +/* + * This is part of Video 4 Linux API. These procedures handle ioctl() calls. + */ +static int pvr2_querycap(struct file *file, void *priv, struct v4l2_capability *cap) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + + strscpy(cap->driver, "pvrusb2", sizeof(cap->driver)); + strscpy(cap->bus_info, pvr2_hdw_get_bus_info(hdw), + sizeof(cap->bus_info)); + strscpy(cap->card, pvr2_hdw_get_desc(hdw), sizeof(cap->card)); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_AUDIO | V4L2_CAP_RADIO | + V4L2_CAP_READWRITE | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int pvr2_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + int val = 0; + int ret; + + ret = pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_STDCUR), &val); + *std = val; + return ret; +} + +static int pvr2_s_std(struct file *file, void *priv, v4l2_std_id std) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + int ret; + + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_STDCUR), std); + pvr2_hdw_commit_ctl(hdw); + return ret; +} + +static int pvr2_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + int val = 0; + int ret; + + ret = pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_STDDETECT), &val); + *std = val; + return ret; +} + +static int pvr2_enum_input(struct file *file, void *priv, struct v4l2_input *vi) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + struct pvr2_ctrl *cptr; + struct v4l2_input tmp; + unsigned int cnt; + int val; + + cptr = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT); + + memset(&tmp, 0, sizeof(tmp)); + tmp.index = vi->index; + if (vi->index >= fh->input_cnt) + return -EINVAL; + val = fh->input_map[vi->index]; + switch (val) { + case PVR2_CVAL_INPUT_TV: + case PVR2_CVAL_INPUT_DTV: + case PVR2_CVAL_INPUT_RADIO: + tmp.type = V4L2_INPUT_TYPE_TUNER; + break; + case PVR2_CVAL_INPUT_SVIDEO: + case PVR2_CVAL_INPUT_COMPOSITE: + tmp.type = V4L2_INPUT_TYPE_CAMERA; + break; + default: + return -EINVAL; + } + + cnt = 0; + pvr2_ctrl_get_valname(cptr, val, + tmp.name, sizeof(tmp.name) - 1, &cnt); + tmp.name[cnt] = 0; + + /* Don't bother with audioset, since this driver currently + always switches the audio whenever the video is + switched. */ + + /* Handling std is a tougher problem. It doesn't make + sense in cases where a device might be multi-standard. + We could just copy out the current value for the + standard, but it can change over time. For now just + leave it zero. */ + *vi = tmp; + return 0; +} + +static int pvr2_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + unsigned int idx; + struct pvr2_ctrl *cptr; + int val; + int ret; + + cptr = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT); + val = 0; + ret = pvr2_ctrl_get_value(cptr, &val); + *i = 0; + for (idx = 0; idx < fh->input_cnt; idx++) { + if (fh->input_map[idx] == val) { + *i = idx; + break; + } + } + return ret; +} + +static int pvr2_s_input(struct file *file, void *priv, unsigned int inp) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + int ret; + + if (inp >= fh->input_cnt) + return -EINVAL; + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT), + fh->input_map[inp]); + pvr2_hdw_commit_ctl(hdw); + return ret; +} + +static int pvr2_enumaudio(struct file *file, void *priv, struct v4l2_audio *vin) +{ + /* pkt: FIXME: We are returning one "fake" input here + which could very well be called "whatever_we_like". + This is for apps that want to see an audio input + just to feel comfortable, as well as to test if + it can do stereo or sth. There is actually no guarantee + that the actual audio input cannot change behind the app's + back, but most applications should not mind that either. + + Hopefully, mplayer people will work with us on this (this + whole mess is to support mplayer pvr://), or Hans will come + up with a more standard way to say "we have inputs but we + don 't want you to change them independent of video" which + will sort this mess. + */ + + if (vin->index > 0) + return -EINVAL; + strscpy(vin->name, "PVRUSB2 Audio", sizeof(vin->name)); + vin->capability = V4L2_AUDCAP_STEREO; + return 0; +} + +static int pvr2_g_audio(struct file *file, void *priv, struct v4l2_audio *vin) +{ + /* pkt: FIXME: see above comment (VIDIOC_ENUMAUDIO) */ + vin->index = 0; + strscpy(vin->name, "PVRUSB2 Audio", sizeof(vin->name)); + vin->capability = V4L2_AUDCAP_STEREO; + return 0; +} + +static int pvr2_s_audio(struct file *file, void *priv, const struct v4l2_audio *vout) +{ + if (vout->index) + return -EINVAL; + return 0; +} + +static int pvr2_g_tuner(struct file *file, void *priv, struct v4l2_tuner *vt) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + + if (vt->index != 0) + return -EINVAL; /* Only answer for the 1st tuner */ + + pvr2_hdw_execute_tuner_poll(hdw); + return pvr2_hdw_get_tuner_status(hdw, vt); +} + +static int pvr2_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *vt) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + int ret; + + if (vt->index != 0) + return -EINVAL; + + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_AUDIOMODE), + vt->audmode); + pvr2_hdw_commit_ctl(hdw); + return ret; +} + +static int pvr2_s_frequency(struct file *file, void *priv, const struct v4l2_frequency *vf) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + unsigned long fv; + struct v4l2_tuner vt; + int cur_input; + struct pvr2_ctrl *ctrlp; + int ret; + + ret = pvr2_hdw_get_tuner_status(hdw, &vt); + if (ret != 0) + return ret; + ctrlp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT); + ret = pvr2_ctrl_get_value(ctrlp, &cur_input); + if (ret != 0) + return ret; + if (vf->type == V4L2_TUNER_RADIO) { + if (cur_input != PVR2_CVAL_INPUT_RADIO) + pvr2_ctrl_set_value(ctrlp, PVR2_CVAL_INPUT_RADIO); + } else { + if (cur_input == PVR2_CVAL_INPUT_RADIO) + pvr2_ctrl_set_value(ctrlp, PVR2_CVAL_INPUT_TV); + } + fv = vf->frequency; + if (vt.capability & V4L2_TUNER_CAP_LOW) + fv = (fv * 125) / 2; + else + fv = fv * 62500; + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_FREQUENCY),fv); + pvr2_hdw_commit_ctl(hdw); + return ret; +} + +static int pvr2_g_frequency(struct file *file, void *priv, struct v4l2_frequency *vf) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + int val = 0; + int cur_input; + struct v4l2_tuner vt; + int ret; + + ret = pvr2_hdw_get_tuner_status(hdw, &vt); + if (ret != 0) + return ret; + ret = pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_FREQUENCY), + &val); + if (ret != 0) + return ret; + pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT), + &cur_input); + if (cur_input == PVR2_CVAL_INPUT_RADIO) + vf->type = V4L2_TUNER_RADIO; + else + vf->type = V4L2_TUNER_ANALOG_TV; + if (vt.capability & V4L2_TUNER_CAP_LOW) + val = (val * 2) / 125; + else + val /= 62500; + vf->frequency = val; + return 0; +} + +static int pvr2_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *fd) +{ + /* Only one format is supported: MPEG. */ + if (fd->index) + return -EINVAL; + + fd->pixelformat = V4L2_PIX_FMT_MPEG; + return 0; +} + +static int pvr2_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *vf) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + int val; + + memcpy(vf, &pvr_format[PVR_FORMAT_PIX], sizeof(struct v4l2_format)); + val = 0; + pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_HRES), + &val); + vf->fmt.pix.width = val; + val = 0; + pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_VRES), + &val); + vf->fmt.pix.height = val; + return 0; +} + +static int pvr2_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *vf) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + int lmin, lmax, ldef; + struct pvr2_ctrl *hcp, *vcp; + int h = vf->fmt.pix.height; + int w = vf->fmt.pix.width; + + hcp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_HRES); + vcp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_VRES); + + lmin = pvr2_ctrl_get_min(hcp); + lmax = pvr2_ctrl_get_max(hcp); + pvr2_ctrl_get_def(hcp, &ldef); + if (w == -1) + w = ldef; + else if (w < lmin) + w = lmin; + else if (w > lmax) + w = lmax; + lmin = pvr2_ctrl_get_min(vcp); + lmax = pvr2_ctrl_get_max(vcp); + pvr2_ctrl_get_def(vcp, &ldef); + if (h == -1) + h = ldef; + else if (h < lmin) + h = lmin; + else if (h > lmax) + h = lmax; + + memcpy(vf, &pvr_format[PVR_FORMAT_PIX], + sizeof(struct v4l2_format)); + vf->fmt.pix.width = w; + vf->fmt.pix.height = h; + return 0; +} + +static int pvr2_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *vf) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + struct pvr2_ctrl *hcp, *vcp; + int ret = pvr2_try_fmt_vid_cap(file, fh, vf); + + if (ret) + return ret; + hcp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_HRES); + vcp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_VRES); + pvr2_ctrl_set_value(hcp, vf->fmt.pix.width); + pvr2_ctrl_set_value(vcp, vf->fmt.pix.height); + pvr2_hdw_commit_ctl(hdw); + return 0; +} + +static int pvr2_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + struct pvr2_v4l2_dev *pdi = fh->pdi; + int ret; + + if (!fh->pdi->stream) { + /* No stream defined for this node. This means + that we're not currently allowed to stream from + this node. */ + return -EPERM; + } + ret = pvr2_hdw_set_stream_type(hdw, pdi->config); + if (ret < 0) + return ret; + return pvr2_hdw_set_streaming(hdw, !0); +} + +static int pvr2_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + + if (!fh->pdi->stream) { + /* No stream defined for this node. This means + that we're not currently allowed to stream from + this node. */ + return -EPERM; + } + return pvr2_hdw_set_streaming(hdw, 0); +} + +static int pvr2_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *vc) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + struct pvr2_ctrl *cptr; + int val; + + if (vc->id & V4L2_CTRL_FLAG_NEXT_CTRL) { + cptr = pvr2_hdw_get_ctrl_nextv4l( + hdw, (vc->id & ~V4L2_CTRL_FLAG_NEXT_CTRL)); + if (cptr) + vc->id = pvr2_ctrl_get_v4lid(cptr); + } else { + cptr = pvr2_hdw_get_ctrl_v4l(hdw, vc->id); + } + if (!cptr) { + pvr2_trace(PVR2_TRACE_V4LIOCTL, + "QUERYCTRL id=0x%x not implemented here", + vc->id); + return -EINVAL; + } + + pvr2_trace(PVR2_TRACE_V4LIOCTL, + "QUERYCTRL id=0x%x mapping name=%s (%s)", + vc->id, pvr2_ctrl_get_name(cptr), + pvr2_ctrl_get_desc(cptr)); + strscpy(vc->name, pvr2_ctrl_get_desc(cptr), sizeof(vc->name)); + vc->flags = pvr2_ctrl_get_v4lflags(cptr); + pvr2_ctrl_get_def(cptr, &val); + vc->default_value = val; + switch (pvr2_ctrl_get_type(cptr)) { + case pvr2_ctl_enum: + vc->type = V4L2_CTRL_TYPE_MENU; + vc->minimum = 0; + vc->maximum = pvr2_ctrl_get_cnt(cptr) - 1; + vc->step = 1; + break; + case pvr2_ctl_bool: + vc->type = V4L2_CTRL_TYPE_BOOLEAN; + vc->minimum = 0; + vc->maximum = 1; + vc->step = 1; + break; + case pvr2_ctl_int: + vc->type = V4L2_CTRL_TYPE_INTEGER; + vc->minimum = pvr2_ctrl_get_min(cptr); + vc->maximum = pvr2_ctrl_get_max(cptr); + vc->step = 1; + break; + default: + pvr2_trace(PVR2_TRACE_V4LIOCTL, + "QUERYCTRL id=0x%x name=%s not mappable", + vc->id, pvr2_ctrl_get_name(cptr)); + return -EINVAL; + } + return 0; +} + +static int pvr2_querymenu(struct file *file, void *priv, struct v4l2_querymenu *vm) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + unsigned int cnt = 0; + int ret; + + ret = pvr2_ctrl_get_valname(pvr2_hdw_get_ctrl_v4l(hdw, vm->id), + vm->index, + vm->name, sizeof(vm->name) - 1, + &cnt); + vm->name[cnt] = 0; + return ret; +} + +static int pvr2_g_ctrl(struct file *file, void *priv, struct v4l2_control *vc) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + int val = 0; + int ret; + + ret = pvr2_ctrl_get_value(pvr2_hdw_get_ctrl_v4l(hdw, vc->id), + &val); + vc->value = val; + return ret; +} + +static int pvr2_s_ctrl(struct file *file, void *priv, struct v4l2_control *vc) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + int ret; + + ret = pvr2_ctrl_set_value(pvr2_hdw_get_ctrl_v4l(hdw, vc->id), + vc->value); + pvr2_hdw_commit_ctl(hdw); + return ret; +} + +static int pvr2_g_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctls) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + struct v4l2_ext_control *ctrl; + struct pvr2_ctrl *cptr; + unsigned int idx; + int val; + int ret; + + ret = 0; + for (idx = 0; idx < ctls->count; idx++) { + ctrl = ctls->controls + idx; + cptr = pvr2_hdw_get_ctrl_v4l(hdw, ctrl->id); + if (cptr) { + if (ctls->which == V4L2_CTRL_WHICH_DEF_VAL) + pvr2_ctrl_get_def(cptr, &val); + else + ret = pvr2_ctrl_get_value(cptr, &val); + } else + ret = -EINVAL; + + if (ret) { + ctls->error_idx = idx; + return ret; + } + /* Ensure that if read as a 64 bit value, the user + will still get a hopefully sane value */ + ctrl->value64 = 0; + ctrl->value = val; + } + return 0; +} + +static int pvr2_s_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctls) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + struct v4l2_ext_control *ctrl; + unsigned int idx; + int ret; + + ret = 0; + for (idx = 0; idx < ctls->count; idx++) { + ctrl = ctls->controls + idx; + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_v4l(hdw, ctrl->id), + ctrl->value); + if (ret) { + ctls->error_idx = idx; + goto commit; + } + } +commit: + pvr2_hdw_commit_ctl(hdw); + return ret; +} + +static int pvr2_try_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctls) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + struct v4l2_ext_control *ctrl; + struct pvr2_ctrl *pctl; + unsigned int idx; + + /* For the moment just validate that the requested control + actually exists. */ + for (idx = 0; idx < ctls->count; idx++) { + ctrl = ctls->controls + idx; + pctl = pvr2_hdw_get_ctrl_v4l(hdw, ctrl->id); + if (!pctl) { + ctls->error_idx = idx; + return -EINVAL; + } + } + return 0; +} + +static int pvr2_g_pixelaspect(struct file *file, void *priv, + int type, struct v4l2_fract *f) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + struct v4l2_cropcap cap = { .type = type }; + int ret; + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + ret = pvr2_hdw_get_cropcap(hdw, &cap); + if (!ret) + *f = cap.pixelaspect; + return ret; +} + +static int pvr2_g_selection(struct file *file, void *priv, + struct v4l2_selection *sel) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + struct v4l2_cropcap cap; + int val = 0; + int ret; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + cap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + ret = pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPL), &val); + if (ret != 0) + return -EINVAL; + sel->r.left = val; + ret = pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPT), &val); + if (ret != 0) + return -EINVAL; + sel->r.top = val; + ret = pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPW), &val); + if (ret != 0) + return -EINVAL; + sel->r.width = val; + ret = pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPH), &val); + if (ret != 0) + return -EINVAL; + sel->r.height = val; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + ret = pvr2_hdw_get_cropcap(hdw, &cap); + sel->r = cap.defrect; + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + ret = pvr2_hdw_get_cropcap(hdw, &cap); + sel->r = cap.bounds; + break; + default: + return -EINVAL; + } + return ret; +} + +static int pvr2_s_selection(struct file *file, void *priv, + struct v4l2_selection *sel) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + int ret; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPL), + sel->r.left); + if (ret != 0) + goto commit; + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPT), + sel->r.top); + if (ret != 0) + goto commit; + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPW), + sel->r.width); + if (ret != 0) + goto commit; + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPH), + sel->r.height); +commit: + pvr2_hdw_commit_ctl(hdw); + return ret; +} + +static int pvr2_log_status(struct file *file, void *priv) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + + pvr2_hdw_trigger_module_log(hdw); + return 0; +} + +static const struct v4l2_ioctl_ops pvr2_ioctl_ops = { + .vidioc_querycap = pvr2_querycap, + .vidioc_s_audio = pvr2_s_audio, + .vidioc_g_audio = pvr2_g_audio, + .vidioc_enumaudio = pvr2_enumaudio, + .vidioc_enum_input = pvr2_enum_input, + .vidioc_g_pixelaspect = pvr2_g_pixelaspect, + .vidioc_s_selection = pvr2_s_selection, + .vidioc_g_selection = pvr2_g_selection, + .vidioc_g_input = pvr2_g_input, + .vidioc_s_input = pvr2_s_input, + .vidioc_g_frequency = pvr2_g_frequency, + .vidioc_s_frequency = pvr2_s_frequency, + .vidioc_s_tuner = pvr2_s_tuner, + .vidioc_g_tuner = pvr2_g_tuner, + .vidioc_g_std = pvr2_g_std, + .vidioc_s_std = pvr2_s_std, + .vidioc_querystd = pvr2_querystd, + .vidioc_log_status = pvr2_log_status, + .vidioc_enum_fmt_vid_cap = pvr2_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = pvr2_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = pvr2_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = pvr2_try_fmt_vid_cap, + .vidioc_streamon = pvr2_streamon, + .vidioc_streamoff = pvr2_streamoff, + .vidioc_queryctrl = pvr2_queryctrl, + .vidioc_querymenu = pvr2_querymenu, + .vidioc_g_ctrl = pvr2_g_ctrl, + .vidioc_s_ctrl = pvr2_s_ctrl, + .vidioc_g_ext_ctrls = pvr2_g_ext_ctrls, + .vidioc_s_ext_ctrls = pvr2_s_ext_ctrls, + .vidioc_try_ext_ctrls = pvr2_try_ext_ctrls, +}; + +static void pvr2_v4l2_dev_destroy(struct pvr2_v4l2_dev *dip) +{ + struct pvr2_hdw *hdw = dip->v4lp->channel.mc_head->hdw; + enum pvr2_config cfg = dip->config; + char msg[80]; + unsigned int mcnt; + + /* Construct the unregistration message *before* we actually + perform the unregistration step. By doing it this way we don't + have to worry about potentially touching deleted resources. */ + mcnt = scnprintf(msg, sizeof(msg) - 1, + "pvrusb2: unregistered device %s [%s]", + video_device_node_name(&dip->devbase), + pvr2_config_get_name(cfg)); + msg[mcnt] = 0; + + pvr2_hdw_v4l_store_minor_number(hdw,dip->minor_type,-1); + + /* Paranoia */ + dip->v4lp = NULL; + dip->stream = NULL; + + /* Actual deallocation happens later when all internal references + are gone. */ + video_unregister_device(&dip->devbase); + + pr_info("%s\n", msg); + +} + + +static void pvr2_v4l2_dev_disassociate_parent(struct pvr2_v4l2_dev *dip) +{ + if (!dip) return; + if (!dip->devbase.v4l2_dev->dev) return; + dip->devbase.v4l2_dev->dev = NULL; + device_move(&dip->devbase.dev, NULL, DPM_ORDER_NONE); +} + + +static void pvr2_v4l2_destroy_no_lock(struct pvr2_v4l2 *vp) +{ + if (vp->dev_video) { + pvr2_v4l2_dev_destroy(vp->dev_video); + vp->dev_video = NULL; + } + if (vp->dev_radio) { + pvr2_v4l2_dev_destroy(vp->dev_radio); + vp->dev_radio = NULL; + } + + pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_v4l2 id=%p",vp); + pvr2_channel_done(&vp->channel); + kfree(vp); +} + + +static void pvr2_video_device_release(struct video_device *vdev) +{ + struct pvr2_v4l2_dev *dev; + dev = container_of(vdev,struct pvr2_v4l2_dev,devbase); + kfree(dev); +} + + +static void pvr2_v4l2_internal_check(struct pvr2_channel *chp) +{ + struct pvr2_v4l2 *vp; + vp = container_of(chp,struct pvr2_v4l2,channel); + if (!vp->channel.mc_head->disconnect_flag) return; + pvr2_v4l2_dev_disassociate_parent(vp->dev_video); + pvr2_v4l2_dev_disassociate_parent(vp->dev_radio); + if (!list_empty(&vp->dev_video->devbase.fh_list) || + (vp->dev_radio && + !list_empty(&vp->dev_radio->devbase.fh_list))) { + pvr2_trace(PVR2_TRACE_STRUCT, + "pvr2_v4l2 internal_check exit-empty id=%p", vp); + return; + } + pvr2_v4l2_destroy_no_lock(vp); +} + + +static int pvr2_v4l2_release(struct file *file) +{ + struct pvr2_v4l2_fh *fhp = file->private_data; + struct pvr2_v4l2 *vp = fhp->pdi->v4lp; + struct pvr2_hdw *hdw = fhp->channel.mc_head->hdw; + + pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_release"); + + if (fhp->rhp) { + struct pvr2_stream *sp; + pvr2_hdw_set_streaming(hdw,0); + sp = pvr2_ioread_get_stream(fhp->rhp); + if (sp) pvr2_stream_set_callback(sp,NULL,NULL); + pvr2_ioread_destroy(fhp->rhp); + fhp->rhp = NULL; + } + + v4l2_fh_del(&fhp->fh); + v4l2_fh_exit(&fhp->fh); + file->private_data = NULL; + + pvr2_channel_done(&fhp->channel); + pvr2_trace(PVR2_TRACE_STRUCT, + "Destroying pvr_v4l2_fh id=%p",fhp); + if (fhp->input_map) { + kfree(fhp->input_map); + fhp->input_map = NULL; + } + kfree(fhp); + if (vp->channel.mc_head->disconnect_flag && + list_empty(&vp->dev_video->devbase.fh_list) && + (!vp->dev_radio || + list_empty(&vp->dev_radio->devbase.fh_list))) { + pvr2_v4l2_destroy_no_lock(vp); + } + return 0; +} + + +static int pvr2_v4l2_open(struct file *file) +{ + struct pvr2_v4l2_dev *dip; /* Our own context pointer */ + struct pvr2_v4l2_fh *fhp; + struct pvr2_v4l2 *vp; + struct pvr2_hdw *hdw; + unsigned int input_mask = 0; + unsigned int input_cnt,idx; + int ret = 0; + + dip = container_of(video_devdata(file),struct pvr2_v4l2_dev,devbase); + + vp = dip->v4lp; + hdw = vp->channel.hdw; + + pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_open"); + + if (!pvr2_hdw_dev_ok(hdw)) { + pvr2_trace(PVR2_TRACE_OPEN_CLOSE, + "pvr2_v4l2_open: hardware not ready"); + return -EIO; + } + + fhp = kzalloc(sizeof(*fhp),GFP_KERNEL); + if (!fhp) { + return -ENOMEM; + } + + v4l2_fh_init(&fhp->fh, &dip->devbase); + init_waitqueue_head(&fhp->wait_data); + fhp->pdi = dip; + + pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr_v4l2_fh id=%p",fhp); + pvr2_channel_init(&fhp->channel,vp->channel.mc_head); + + if (dip->v4l_type == VFL_TYPE_RADIO) { + /* Opening device as a radio, legal input selection subset + is just the radio. */ + input_mask = (1 << PVR2_CVAL_INPUT_RADIO); + } else { + /* Opening the main V4L device, legal input selection + subset includes all analog inputs. */ + input_mask = ((1 << PVR2_CVAL_INPUT_RADIO) | + (1 << PVR2_CVAL_INPUT_TV) | + (1 << PVR2_CVAL_INPUT_COMPOSITE) | + (1 << PVR2_CVAL_INPUT_SVIDEO)); + } + ret = pvr2_channel_limit_inputs(&fhp->channel,input_mask); + if (ret) { + pvr2_channel_done(&fhp->channel); + pvr2_trace(PVR2_TRACE_STRUCT, + "Destroying pvr_v4l2_fh id=%p (input mask error)", + fhp); + v4l2_fh_exit(&fhp->fh); + kfree(fhp); + return ret; + } + + input_mask &= pvr2_hdw_get_input_available(hdw); + input_cnt = 0; + for (idx = 0; idx < (sizeof(input_mask) << 3); idx++) { + if (input_mask & (1UL << idx)) input_cnt++; + } + fhp->input_cnt = input_cnt; + fhp->input_map = kzalloc(input_cnt,GFP_KERNEL); + if (!fhp->input_map) { + pvr2_channel_done(&fhp->channel); + pvr2_trace(PVR2_TRACE_STRUCT, + "Destroying pvr_v4l2_fh id=%p (input map failure)", + fhp); + v4l2_fh_exit(&fhp->fh); + kfree(fhp); + return -ENOMEM; + } + input_cnt = 0; + for (idx = 0; idx < (sizeof(input_mask) << 3); idx++) { + if (!(input_mask & (1UL << idx))) continue; + fhp->input_map[input_cnt++] = idx; + } + + fhp->file = file; + file->private_data = fhp; + + fhp->fw_mode_flag = pvr2_hdw_cpufw_get_enabled(hdw); + v4l2_fh_add(&fhp->fh); + + return 0; +} + + +static void pvr2_v4l2_notify(struct pvr2_v4l2_fh *fhp) +{ + wake_up(&fhp->wait_data); +} + +static int pvr2_v4l2_iosetup(struct pvr2_v4l2_fh *fh) +{ + int ret; + struct pvr2_stream *sp; + struct pvr2_hdw *hdw; + if (fh->rhp) return 0; + + if (!fh->pdi->stream) { + /* No stream defined for this node. This means that we're + not currently allowed to stream from this node. */ + return -EPERM; + } + + /* First read() attempt. Try to claim the stream and start + it... */ + if ((ret = pvr2_channel_claim_stream(&fh->channel, + fh->pdi->stream)) != 0) { + /* Someone else must already have it */ + return ret; + } + + fh->rhp = pvr2_channel_create_mpeg_stream(fh->pdi->stream); + if (!fh->rhp) { + pvr2_channel_claim_stream(&fh->channel,NULL); + return -ENOMEM; + } + + hdw = fh->channel.mc_head->hdw; + sp = fh->pdi->stream->stream; + pvr2_stream_set_callback(sp,(pvr2_stream_callback)pvr2_v4l2_notify,fh); + pvr2_hdw_set_stream_type(hdw,fh->pdi->config); + if ((ret = pvr2_hdw_set_streaming(hdw,!0)) < 0) return ret; + return pvr2_ioread_set_enabled(fh->rhp,!0); +} + + +static ssize_t pvr2_v4l2_read(struct file *file, + char __user *buff, size_t count, loff_t *ppos) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + int ret; + + if (fh->fw_mode_flag) { + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + char *tbuf; + int c1,c2; + int tcnt = 0; + unsigned int offs = *ppos; + + tbuf = kmalloc(PAGE_SIZE,GFP_KERNEL); + if (!tbuf) return -ENOMEM; + + while (count) { + c1 = count; + if (c1 > PAGE_SIZE) c1 = PAGE_SIZE; + c2 = pvr2_hdw_cpufw_get(hdw,offs,tbuf,c1); + if (c2 < 0) { + tcnt = c2; + break; + } + if (!c2) break; + if (copy_to_user(buff,tbuf,c2)) { + tcnt = -EFAULT; + break; + } + offs += c2; + tcnt += c2; + buff += c2; + count -= c2; + *ppos += c2; + } + kfree(tbuf); + return tcnt; + } + + if (!fh->rhp) { + ret = pvr2_v4l2_iosetup(fh); + if (ret) { + return ret; + } + } + + for (;;) { + ret = pvr2_ioread_read(fh->rhp,buff,count); + if (ret >= 0) break; + if (ret != -EAGAIN) break; + if (file->f_flags & O_NONBLOCK) break; + /* Doing blocking I/O. Wait here. */ + ret = wait_event_interruptible( + fh->wait_data, + pvr2_ioread_avail(fh->rhp) >= 0); + if (ret < 0) break; + } + + return ret; +} + + +static __poll_t pvr2_v4l2_poll(struct file *file, poll_table *wait) +{ + __poll_t mask = 0; + struct pvr2_v4l2_fh *fh = file->private_data; + int ret; + + if (fh->fw_mode_flag) { + mask |= EPOLLIN | EPOLLRDNORM; + return mask; + } + + if (!fh->rhp) { + ret = pvr2_v4l2_iosetup(fh); + if (ret) return EPOLLERR; + } + + poll_wait(file,&fh->wait_data,wait); + + if (pvr2_ioread_avail(fh->rhp) >= 0) { + mask |= EPOLLIN | EPOLLRDNORM; + } + + return mask; +} + + +static const struct v4l2_file_operations vdev_fops = { + .owner = THIS_MODULE, + .open = pvr2_v4l2_open, + .release = pvr2_v4l2_release, + .read = pvr2_v4l2_read, + .unlocked_ioctl = video_ioctl2, + .poll = pvr2_v4l2_poll, +}; + + +static const struct video_device vdev_template = { + .fops = &vdev_fops, +}; + + +static void pvr2_v4l2_dev_init(struct pvr2_v4l2_dev *dip, + struct pvr2_v4l2 *vp, + int v4l_type) +{ + int mindevnum; + int unit_number; + struct pvr2_hdw *hdw; + int *nr_ptr = NULL; + u32 caps = V4L2_CAP_TUNER | V4L2_CAP_READWRITE; + + dip->v4lp = vp; + + hdw = vp->channel.mc_head->hdw; + dip->v4l_type = v4l_type; + switch (v4l_type) { + case VFL_TYPE_VIDEO: + dip->stream = &vp->channel.mc_head->video_stream; + dip->config = pvr2_config_mpeg; + dip->minor_type = pvr2_v4l_type_video; + nr_ptr = video_nr; + caps |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_AUDIO; + if (!dip->stream) { + pr_err(KBUILD_MODNAME + ": Failed to set up pvrusb2 v4l video dev due to missing stream instance\n"); + return; + } + break; + case VFL_TYPE_VBI: + dip->config = pvr2_config_vbi; + dip->minor_type = pvr2_v4l_type_vbi; + nr_ptr = vbi_nr; + caps |= V4L2_CAP_VBI_CAPTURE; + break; + case VFL_TYPE_RADIO: + dip->stream = &vp->channel.mc_head->video_stream; + dip->config = pvr2_config_mpeg; + dip->minor_type = pvr2_v4l_type_radio; + nr_ptr = radio_nr; + caps |= V4L2_CAP_RADIO; + break; + default: + /* Bail out (this should be impossible) */ + pr_err(KBUILD_MODNAME ": Failed to set up pvrusb2 v4l dev due to unrecognized config\n"); + return; + } + + dip->devbase = vdev_template; + dip->devbase.release = pvr2_video_device_release; + dip->devbase.ioctl_ops = &pvr2_ioctl_ops; + dip->devbase.device_caps = caps; + { + int val; + pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw, + PVR2_CID_STDAVAIL), &val); + dip->devbase.tvnorms = (v4l2_std_id)val; + } + + mindevnum = -1; + unit_number = pvr2_hdw_get_unit_number(hdw); + if (nr_ptr && (unit_number >= 0) && (unit_number < PVR_NUM)) { + mindevnum = nr_ptr[unit_number]; + } + pvr2_hdw_set_v4l2_dev(hdw, &dip->devbase); + if ((video_register_device(&dip->devbase, + dip->v4l_type, mindevnum) < 0) && + (video_register_device(&dip->devbase, + dip->v4l_type, -1) < 0)) { + pr_err(KBUILD_MODNAME + ": Failed to register pvrusb2 v4l device\n"); + } + + pr_info("pvrusb2: registered device %s [%s]\n", + video_device_node_name(&dip->devbase), + pvr2_config_get_name(dip->config)); + + pvr2_hdw_v4l_store_minor_number(hdw, + dip->minor_type,dip->devbase.minor); +} + + +struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *mnp) +{ + struct pvr2_v4l2 *vp; + + vp = kzalloc(sizeof(*vp),GFP_KERNEL); + if (!vp) return vp; + pvr2_channel_init(&vp->channel,mnp); + pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_v4l2 id=%p",vp); + + vp->channel.check_func = pvr2_v4l2_internal_check; + + /* register streams */ + vp->dev_video = kzalloc(sizeof(*vp->dev_video),GFP_KERNEL); + if (!vp->dev_video) goto fail; + pvr2_v4l2_dev_init(vp->dev_video,vp,VFL_TYPE_VIDEO); + if (pvr2_hdw_get_input_available(vp->channel.mc_head->hdw) & + (1 << PVR2_CVAL_INPUT_RADIO)) { + vp->dev_radio = kzalloc(sizeof(*vp->dev_radio),GFP_KERNEL); + if (!vp->dev_radio) goto fail; + pvr2_v4l2_dev_init(vp->dev_radio,vp,VFL_TYPE_RADIO); + } + + return vp; + fail: + pvr2_trace(PVR2_TRACE_STRUCT,"Failure creating pvr2_v4l2 id=%p",vp); + pvr2_v4l2_destroy_no_lock(vp); + return NULL; +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-v4l2.h b/drivers/media/usb/pvrusb2/pvrusb2-v4l2.h new file mode 100644 index 0000000000..e1f9f09b6d --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-v4l2.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + */ +#ifndef __PVRUSB2_V4L2_H +#define __PVRUSB2_V4L2_H + +#include "pvrusb2-context.h" + +struct pvr2_v4l2; + +struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *); + +#endif /* __PVRUSB2_V4L2_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.c b/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.c new file mode 100644 index 0000000000..16dd3e8599 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +/* + + This source file is specifically designed to interface with the + saa711x support that is available in the v4l available starting + with linux 2.6.15. + +*/ + +#include "pvrusb2-video-v4l.h" + + + +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/i2c/saa7115.h> +#include <linux/errno.h> + +struct routing_scheme { + const int *def; + unsigned int cnt; +}; + + +static const int routing_scheme0[] = { + [PVR2_CVAL_INPUT_TV] = SAA7115_COMPOSITE4, + /* In radio mode, we mute the video, but point at one + spot just to stay consistent */ + [PVR2_CVAL_INPUT_RADIO] = SAA7115_COMPOSITE5, + [PVR2_CVAL_INPUT_COMPOSITE] = SAA7115_COMPOSITE5, + [PVR2_CVAL_INPUT_SVIDEO] = SAA7115_SVIDEO2, +}; + +static const struct routing_scheme routing_def0 = { + .def = routing_scheme0, + .cnt = ARRAY_SIZE(routing_scheme0), +}; + +static const int routing_scheme1[] = { + [PVR2_CVAL_INPUT_TV] = SAA7115_COMPOSITE4, + [PVR2_CVAL_INPUT_RADIO] = SAA7115_COMPOSITE5, + [PVR2_CVAL_INPUT_COMPOSITE] = SAA7115_COMPOSITE3, + [PVR2_CVAL_INPUT_SVIDEO] = SAA7115_SVIDEO2, /* or SVIDEO0, it seems */ +}; + +static const struct routing_scheme routing_def1 = { + .def = routing_scheme1, + .cnt = ARRAY_SIZE(routing_scheme1), +}; + +static const struct routing_scheme *routing_schemes[] = { + [PVR2_ROUTING_SCHEME_HAUPPAUGE] = &routing_def0, + [PVR2_ROUTING_SCHEME_ONAIR] = &routing_def1, +}; + +void pvr2_saa7115_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd) +{ + if (hdw->input_dirty || hdw->force_dirty) { + const struct routing_scheme *sp; + unsigned int sid = hdw->hdw_desc->signal_routing_scheme; + u32 input; + + pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_input(%d)", + hdw->input_val); + + sp = (sid < ARRAY_SIZE(routing_schemes)) ? + routing_schemes[sid] : NULL; + if ((sp == NULL) || + (hdw->input_val < 0) || + (hdw->input_val >= sp->cnt)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "*** WARNING *** subdev v4l2 set_input: Invalid routing scheme (%u) and/or input (%d)", + sid, hdw->input_val); + return; + } + input = sp->def[hdw->input_val]; + sd->ops->video->s_routing(sd, input, 0, 0); + } +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.h b/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.h new file mode 100644 index 0000000000..2a1776ba4b --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#ifndef __PVRUSB2_VIDEO_V4L_H +#define __PVRUSB2_VIDEO_V4L_H + +/* + + This module connects the pvrusb2 driver to the I2C chip level + driver which handles device video processing. This interface is + used internally by the driver; higher level code should only + interact through the interface provided by pvrusb2-hdw.h. + +*/ + + +#include "pvrusb2-hdw-internal.h" +void pvr2_saa7115_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *); + +#endif /* __PVRUSB2_VIDEO_V4L_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2-wm8775.c b/drivers/media/usb/pvrusb2/pvrusb2-wm8775.c new file mode 100644 index 0000000000..5f800f4a82 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-wm8775.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +/* + + This source file is specifically designed to interface with the + wm8775. + +*/ + +#include "pvrusb2-wm8775.h" + + +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <linux/errno.h> + +void pvr2_wm8775_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd) +{ + if (hdw->input_dirty || hdw->force_dirty) { + u32 input; + + switch (hdw->input_val) { + case PVR2_CVAL_INPUT_RADIO: + input = 1; + break; + default: + /* All other cases just use the second input */ + input = 2; + break; + } + pvr2_trace(PVR2_TRACE_CHIPS, "subdev wm8775 set_input(val=%d route=0x%x)", + hdw->input_val, input); + + sd->ops->audio->s_routing(sd, input, 0, 0); + } +} diff --git a/drivers/media/usb/pvrusb2/pvrusb2-wm8775.h b/drivers/media/usb/pvrusb2/pvrusb2-wm8775.h new file mode 100644 index 0000000000..806d8b7585 --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2-wm8775.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#ifndef __PVRUSB2_WM8775_H +#define __PVRUSB2_WM8775_H + +/* + + This module connects the pvrusb2 driver to the I2C chip level + driver which performs analog -> digital audio conversion for + external audio inputs. This interface is used internally by the + driver; higher level code should only interact through the + interface provided by pvrusb2-hdw.h. + +*/ + + + +#include "pvrusb2-hdw-internal.h" + +void pvr2_wm8775_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *sd); + + +#endif /* __PVRUSB2_WM8775_H */ diff --git a/drivers/media/usb/pvrusb2/pvrusb2.h b/drivers/media/usb/pvrusb2/pvrusb2.h new file mode 100644 index 0000000000..1185b72a5f --- /dev/null +++ b/drivers/media/usb/pvrusb2/pvrusb2.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + */ + +#ifndef __PVRUSB2_H +#define __PVRUSB2_H + +/* Maximum number of pvrusb2 instances we can track at once. You + might want to increase this - however the driver operation will not + be impaired if it is too small. Instead additional units just + won't have an ID assigned and it might not be possible to specify + module parameters for those extra units. */ +#define PVR_NUM 20 + +#endif /* __PVRUSB2_H */ |