summaryrefslogtreecommitdiffstats
path: root/drivers/media/video/cx18/cx18-fileops.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/video/cx18/cx18-fileops.c')
-rw-r--r--drivers/media/video/cx18/cx18-fileops.c711
1 files changed, 711 insertions, 0 deletions
diff --git a/drivers/media/video/cx18/cx18-fileops.c b/drivers/media/video/cx18/cx18-fileops.c
new file mode 100644
index 00000000000..69303065a29
--- /dev/null
+++ b/drivers/media/video/cx18/cx18-fileops.c
@@ -0,0 +1,711 @@
+/*
+ * cx18 file operation functions
+ *
+ * Derived from ivtv-fileops.c
+ *
+ * Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ */
+
+#include "cx18-driver.h"
+#include "cx18-fileops.h"
+#include "cx18-i2c.h"
+#include "cx18-queue.h"
+#include "cx18-vbi.h"
+#include "cx18-audio.h"
+#include "cx18-mailbox.h"
+#include "cx18-scb.h"
+#include "cx18-streams.h"
+#include "cx18-controls.h"
+#include "cx18-ioctl.h"
+#include "cx18-cards.h"
+
+/* This function tries to claim the stream for a specific file descriptor.
+ If no one else is using this stream then the stream is claimed and
+ associated VBI streams are also automatically claimed.
+ Possible error returns: -EBUSY if someone else has claimed
+ the stream or 0 on success. */
+int cx18_claim_stream(struct cx18_open_id *id, int type)
+{
+ struct cx18 *cx = id->cx;
+ struct cx18_stream *s = &cx->streams[type];
+ struct cx18_stream *s_vbi;
+ int vbi_type;
+
+ if (test_and_set_bit(CX18_F_S_CLAIMED, &s->s_flags)) {
+ /* someone already claimed this stream */
+ if (s->id == id->open_id) {
+ /* yes, this file descriptor did. So that's OK. */
+ return 0;
+ }
+ if (s->id == -1 && type == CX18_ENC_STREAM_TYPE_VBI) {
+ /* VBI is handled already internally, now also assign
+ the file descriptor to this stream for external
+ reading of the stream. */
+ s->id = id->open_id;
+ CX18_DEBUG_INFO("Start Read VBI\n");
+ return 0;
+ }
+ /* someone else is using this stream already */
+ CX18_DEBUG_INFO("Stream %d is busy\n", type);
+ return -EBUSY;
+ }
+ s->id = id->open_id;
+
+ /* CX18_DEC_STREAM_TYPE_MPG needs to claim CX18_DEC_STREAM_TYPE_VBI,
+ CX18_ENC_STREAM_TYPE_MPG needs to claim CX18_ENC_STREAM_TYPE_VBI
+ (provided VBI insertion is on and sliced VBI is selected), for all
+ other streams we're done */
+ if (type == CX18_ENC_STREAM_TYPE_MPG &&
+ cx->vbi.insert_mpeg && cx->vbi.sliced_in->service_set) {
+ vbi_type = CX18_ENC_STREAM_TYPE_VBI;
+ } else {
+ return 0;
+ }
+ s_vbi = &cx->streams[vbi_type];
+
+ set_bit(CX18_F_S_CLAIMED, &s_vbi->s_flags);
+
+ /* mark that it is used internally */
+ set_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags);
+ return 0;
+}
+
+/* This function releases a previously claimed stream. It will take into
+ account associated VBI streams. */
+void cx18_release_stream(struct cx18_stream *s)
+{
+ struct cx18 *cx = s->cx;
+ struct cx18_stream *s_vbi;
+
+ s->id = -1;
+ if (s->type == CX18_ENC_STREAM_TYPE_VBI &&
+ test_bit(CX18_F_S_INTERNAL_USE, &s->s_flags)) {
+ /* this stream is still in use internally */
+ return;
+ }
+ if (!test_and_clear_bit(CX18_F_S_CLAIMED, &s->s_flags)) {
+ CX18_DEBUG_WARN("Release stream %s not in use!\n", s->name);
+ return;
+ }
+
+ cx18_flush_queues(s);
+
+ /* CX18_ENC_STREAM_TYPE_MPG needs to release CX18_ENC_STREAM_TYPE_VBI,
+ for all other streams we're done */
+ if (s->type == CX18_ENC_STREAM_TYPE_MPG)
+ s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
+ else
+ return;
+
+ /* clear internal use flag */
+ if (!test_and_clear_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags)) {
+ /* was already cleared */
+ return;
+ }
+ if (s_vbi->id != -1) {
+ /* VBI stream still claimed by a file descriptor */
+ return;
+ }
+ clear_bit(CX18_F_S_CLAIMED, &s_vbi->s_flags);
+ cx18_flush_queues(s_vbi);
+}
+
+static void cx18_dualwatch(struct cx18 *cx)
+{
+ struct v4l2_tuner vt;
+ u16 new_bitmap;
+ u16 new_stereo_mode;
+ const u16 stereo_mask = 0x0300;
+ const u16 dual = 0x0200;
+
+ new_stereo_mode = cx->params.audio_properties & stereo_mask;
+ memset(&vt, 0, sizeof(vt));
+ cx18_call_i2c_clients(cx, VIDIOC_G_TUNER, &vt);
+ if (vt.audmode == V4L2_TUNER_MODE_LANG1_LANG2 &&
+ (vt.rxsubchans & V4L2_TUNER_SUB_LANG2))
+ new_stereo_mode = dual;
+
+ if (new_stereo_mode == cx->dualwatch_stereo_mode)
+ return;
+
+ new_bitmap = new_stereo_mode | (cx->params.audio_properties & ~stereo_mask);
+
+ CX18_DEBUG_INFO("dualwatch: change stereo flag from 0x%x to 0x%x. new audio_bitmask=0x%ux\n",
+ cx->dualwatch_stereo_mode, new_stereo_mode, new_bitmap);
+
+ if (cx18_vapi(cx, CX18_CPU_SET_AUDIO_PARAMETERS, 2,
+ cx18_find_handle(cx), new_bitmap) == 0) {
+ cx->dualwatch_stereo_mode = new_stereo_mode;
+ return;
+ }
+ CX18_DEBUG_INFO("dualwatch: changing stereo flag failed\n");
+}
+
+
+static struct cx18_buffer *cx18_get_buffer(struct cx18_stream *s, int non_block, int *err)
+{
+ struct cx18 *cx = s->cx;
+ struct cx18_stream *s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
+ struct cx18_buffer *buf;
+ DEFINE_WAIT(wait);
+
+ *err = 0;
+ while (1) {
+ if (s->type == CX18_ENC_STREAM_TYPE_MPG) {
+
+ if (time_after(jiffies, cx->dualwatch_jiffies + msecs_to_jiffies(1000))) {
+ cx->dualwatch_jiffies = jiffies;
+ cx18_dualwatch(cx);
+ }
+ if (test_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags) &&
+ !test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) {
+ while ((buf = cx18_dequeue(s_vbi, &s_vbi->q_full))) {
+ /* byteswap and process VBI data */
+/* cx18_process_vbi_data(cx, buf, s_vbi->dma_pts, s_vbi->type); */
+ cx18_enqueue(s_vbi, buf, &s_vbi->q_free);
+ }
+ }
+ buf = &cx->vbi.sliced_mpeg_buf;
+ if (buf->readpos != buf->bytesused)
+ return buf;
+ }
+
+ /* do we have leftover data? */
+ buf = cx18_dequeue(s, &s->q_io);
+ if (buf)
+ return buf;
+
+ /* do we have new data? */
+ buf = cx18_dequeue(s, &s->q_full);
+ if (buf) {
+ if (!test_and_clear_bit(CX18_F_B_NEED_BUF_SWAP,
+ &buf->b_flags))
+ return buf;
+ if (s->type == CX18_ENC_STREAM_TYPE_MPG)
+ /* byteswap MPG data */
+ cx18_buf_swap(buf);
+ else {
+ /* byteswap and process VBI data */
+ cx18_process_vbi_data(cx, buf,
+ s->dma_pts, s->type);
+ }
+ return buf;
+ }
+
+ /* return if end of stream */
+ if (!test_bit(CX18_F_S_STREAMING, &s->s_flags)) {
+ CX18_DEBUG_INFO("EOS %s\n", s->name);
+ return NULL;
+ }
+
+ /* return if file was opened with O_NONBLOCK */
+ if (non_block) {
+ *err = -EAGAIN;
+ return NULL;
+ }
+
+ /* wait for more data to arrive */
+ prepare_to_wait(&s->waitq, &wait, TASK_INTERRUPTIBLE);
+ /* New buffers might have become available before we were added
+ to the waitqueue */
+ if (!s->q_full.buffers)
+ schedule();
+ finish_wait(&s->waitq, &wait);
+ if (signal_pending(current)) {
+ /* return if a signal was received */
+ CX18_DEBUG_INFO("User stopped %s\n", s->name);
+ *err = -EINTR;
+ return NULL;
+ }
+ }
+}
+
+static void cx18_setup_sliced_vbi_buf(struct cx18 *cx)
+{
+ int idx = cx->vbi.inserted_frame % CX18_VBI_FRAMES;
+
+ cx->vbi.sliced_mpeg_buf.buf = cx->vbi.sliced_mpeg_data[idx];
+ cx->vbi.sliced_mpeg_buf.bytesused = cx->vbi.sliced_mpeg_size[idx];
+ cx->vbi.sliced_mpeg_buf.readpos = 0;
+}
+
+static size_t cx18_copy_buf_to_user(struct cx18_stream *s,
+ struct cx18_buffer *buf, char __user *ubuf, size_t ucount)
+{
+ struct cx18 *cx = s->cx;
+ size_t len = buf->bytesused - buf->readpos;
+
+ if (len > ucount)
+ len = ucount;
+ if (cx->vbi.insert_mpeg && s->type == CX18_ENC_STREAM_TYPE_MPG &&
+ cx->vbi.sliced_in->service_set && buf != &cx->vbi.sliced_mpeg_buf) {
+ const char *start = buf->buf + buf->readpos;
+ const char *p = start + 1;
+ const u8 *q;
+ u8 ch = cx->search_pack_header ? 0xba : 0xe0;
+ int stuffing, i;
+
+ while (start + len > p) {
+ q = memchr(p, 0, start + len - p);
+ if (q == NULL)
+ break;
+ p = q + 1;
+ if ((char *)q + 15 >= buf->buf + buf->bytesused ||
+ q[1] != 0 || q[2] != 1 || q[3] != ch)
+ continue;
+ if (!cx->search_pack_header) {
+ if ((q[6] & 0xc0) != 0x80)
+ continue;
+ if (((q[7] & 0xc0) == 0x80 &&
+ (q[9] & 0xf0) == 0x20) ||
+ ((q[7] & 0xc0) == 0xc0 &&
+ (q[9] & 0xf0) == 0x30)) {
+ ch = 0xba;
+ cx->search_pack_header = 1;
+ p = q + 9;
+ }
+ continue;
+ }
+ stuffing = q[13] & 7;
+ /* all stuffing bytes must be 0xff */
+ for (i = 0; i < stuffing; i++)
+ if (q[14 + i] != 0xff)
+ break;
+ if (i == stuffing &&
+ (q[4] & 0xc4) == 0x44 &&
+ (q[12] & 3) == 3 &&
+ q[14 + stuffing] == 0 &&
+ q[15 + stuffing] == 0 &&
+ q[16 + stuffing] == 1) {
+ cx->search_pack_header = 0;
+ len = (char *)q - start;
+ cx18_setup_sliced_vbi_buf(cx);
+ break;
+ }
+ }
+ }
+ if (copy_to_user(ubuf, (u8 *)buf->buf + buf->readpos, len)) {
+ CX18_DEBUG_WARN("copy %zd bytes to user failed for %s\n",
+ len, s->name);
+ return -EFAULT;
+ }
+ buf->readpos += len;
+ if (s->type == CX18_ENC_STREAM_TYPE_MPG &&
+ buf != &cx->vbi.sliced_mpeg_buf)
+ cx->mpg_data_received += len;
+ return len;
+}
+
+static ssize_t cx18_read(struct cx18_stream *s, char __user *ubuf,
+ size_t tot_count, int non_block)
+{
+ struct cx18 *cx = s->cx;
+ size_t tot_written = 0;
+ int single_frame = 0;
+
+ if (atomic_read(&cx->capturing) == 0 && s->id == -1) {
+ /* shouldn't happen */
+ CX18_DEBUG_WARN("Stream %s not initialized before read\n",
+ s->name);
+ return -EIO;
+ }
+
+ /* Each VBI buffer is one frame, the v4l2 API says that for VBI the
+ frames should arrive one-by-one, so make sure we never output more
+ than one VBI frame at a time */
+ if (s->type == CX18_ENC_STREAM_TYPE_VBI &&
+ cx->vbi.sliced_in->service_set)
+ single_frame = 1;
+
+ for (;;) {
+ struct cx18_buffer *buf;
+ int rc;
+
+ buf = cx18_get_buffer(s, non_block, &rc);
+ /* if there is no data available... */
+ if (buf == NULL) {
+ /* if we got data, then return that regardless */
+ if (tot_written)
+ break;
+ /* EOS condition */
+ if (rc == 0) {
+ clear_bit(CX18_F_S_STREAMOFF, &s->s_flags);
+ clear_bit(CX18_F_S_APPL_IO, &s->s_flags);
+ cx18_release_stream(s);
+ }
+ /* set errno */
+ return rc;
+ }
+
+ rc = cx18_copy_buf_to_user(s, buf, ubuf + tot_written,
+ tot_count - tot_written);
+
+ if (buf != &cx->vbi.sliced_mpeg_buf) {
+ if (buf->readpos == buf->bytesused) {
+ cx18_buf_sync_for_device(s, buf);
+ cx18_enqueue(s, buf, &s->q_free);
+ cx18_vapi(cx, CX18_CPU_DE_SET_MDL, 5,
+ s->handle,
+ (void *)&cx->scb->cpu_mdl[buf->id] - cx->enc_mem,
+ 1, buf->id, s->buf_size);
+ } else
+ cx18_enqueue(s, buf, &s->q_io);
+ } else if (buf->readpos == buf->bytesused) {
+ int idx = cx->vbi.inserted_frame % CX18_VBI_FRAMES;
+
+ cx->vbi.sliced_mpeg_size[idx] = 0;
+ cx->vbi.inserted_frame++;
+ cx->vbi_data_inserted += buf->bytesused;
+ }
+ if (rc < 0)
+ return rc;
+ tot_written += rc;
+
+ if (tot_written == tot_count || single_frame)
+ break;
+ }
+ return tot_written;
+}
+
+static ssize_t cx18_read_pos(struct cx18_stream *s, char __user *ubuf,
+ size_t count, loff_t *pos, int non_block)
+{
+ ssize_t rc = count ? cx18_read(s, ubuf, count, non_block) : 0;
+ struct cx18 *cx = s->cx;
+
+ CX18_DEBUG_HI_FILE("read %zd from %s, got %zd\n", count, s->name, rc);
+ if (rc > 0)
+ pos += rc;
+ return rc;
+}
+
+int cx18_start_capture(struct cx18_open_id *id)
+{
+ struct cx18 *cx = id->cx;
+ struct cx18_stream *s = &cx->streams[id->type];
+ struct cx18_stream *s_vbi;
+
+ if (s->type == CX18_ENC_STREAM_TYPE_RAD) {
+ /* you cannot read from these stream types. */
+ return -EPERM;
+ }
+
+ /* Try to claim this stream. */
+ if (cx18_claim_stream(id, s->type))
+ return -EBUSY;
+
+ /* If capture is already in progress, then we also have to
+ do nothing extra. */
+ if (test_bit(CX18_F_S_STREAMOFF, &s->s_flags) ||
+ test_and_set_bit(CX18_F_S_STREAMING, &s->s_flags)) {
+ set_bit(CX18_F_S_APPL_IO, &s->s_flags);
+ return 0;
+ }
+
+ /* Start VBI capture if required */
+ s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
+ if (s->type == CX18_ENC_STREAM_TYPE_MPG &&
+ test_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags) &&
+ !test_and_set_bit(CX18_F_S_STREAMING, &s_vbi->s_flags)) {
+ /* Note: the CX18_ENC_STREAM_TYPE_VBI is claimed
+ automatically when the MPG stream is claimed.
+ We only need to start the VBI capturing. */
+ if (cx18_start_v4l2_encode_stream(s_vbi)) {
+ CX18_DEBUG_WARN("VBI capture start failed\n");
+
+ /* Failure, clean up and return an error */
+ clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags);
+ clear_bit(CX18_F_S_STREAMING, &s->s_flags);
+ /* also releases the associated VBI stream */
+ cx18_release_stream(s);
+ return -EIO;
+ }
+ CX18_DEBUG_INFO("VBI insertion started\n");
+ }
+
+ /* Tell the card to start capturing */
+ if (!cx18_start_v4l2_encode_stream(s)) {
+ /* We're done */
+ set_bit(CX18_F_S_APPL_IO, &s->s_flags);
+ /* Resume a possibly paused encoder */
+ if (test_and_clear_bit(CX18_F_I_ENC_PAUSED, &cx->i_flags))
+ cx18_vapi(cx, CX18_CPU_CAPTURE_PAUSE, 1, s->handle);
+ return 0;
+ }
+
+ /* failure, clean up */
+ CX18_DEBUG_WARN("Failed to start capturing for stream %s\n", s->name);
+
+ /* Note: the CX18_ENC_STREAM_TYPE_VBI is released
+ automatically when the MPG stream is released.
+ We only need to stop the VBI capturing. */
+ if (s->type == CX18_ENC_STREAM_TYPE_MPG &&
+ test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags)) {
+ cx18_stop_v4l2_encode_stream(s_vbi, 0);
+ clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags);
+ }
+ clear_bit(CX18_F_S_STREAMING, &s->s_flags);
+ cx18_release_stream(s);
+ return -EIO;
+}
+
+ssize_t cx18_v4l2_read(struct file *filp, char __user *buf, size_t count,
+ loff_t *pos)
+{
+ struct cx18_open_id *id = filp->private_data;
+ struct cx18 *cx = id->cx;
+ struct cx18_stream *s = &cx->streams[id->type];
+ int rc;
+
+ CX18_DEBUG_HI_FILE("read %zd bytes from %s\n", count, s->name);
+
+ mutex_lock(&cx->serialize_lock);
+ rc = cx18_start_capture(id);
+ mutex_unlock(&cx->serialize_lock);
+ if (rc)
+ return rc;
+ return cx18_read_pos(s, buf, count, pos, filp->f_flags & O_NONBLOCK);
+}
+
+unsigned int cx18_v4l2_enc_poll(struct file *filp, poll_table *wait)
+{
+ struct cx18_open_id *id = filp->private_data;
+ struct cx18 *cx = id->cx;
+ struct cx18_stream *s = &cx->streams[id->type];
+ int eof = test_bit(CX18_F_S_STREAMOFF, &s->s_flags);
+
+ /* Start a capture if there is none */
+ if (!eof && !test_bit(CX18_F_S_STREAMING, &s->s_flags)) {
+ int rc;
+
+ mutex_lock(&cx->serialize_lock);
+ rc = cx18_start_capture(id);
+ mutex_unlock(&cx->serialize_lock);
+ if (rc) {
+ CX18_DEBUG_INFO("Could not start capture for %s (%d)\n",
+ s->name, rc);
+ return POLLERR;
+ }
+ CX18_DEBUG_FILE("Encoder poll started capture\n");
+ }
+
+ /* add stream's waitq to the poll list */
+ CX18_DEBUG_HI_FILE("Encoder poll\n");
+ poll_wait(filp, &s->waitq, wait);
+
+ if (s->q_full.length || s->q_io.length)
+ return POLLIN | POLLRDNORM;
+ if (eof)
+ return POLLHUP;
+ return 0;
+}
+
+void cx18_stop_capture(struct cx18_open_id *id, int gop_end)
+{
+ struct cx18 *cx = id->cx;
+ struct cx18_stream *s = &cx->streams[id->type];
+
+ CX18_DEBUG_IOCTL("close() of %s\n", s->name);
+
+ /* 'Unclaim' this stream */
+
+ /* Stop capturing */
+ if (test_bit(CX18_F_S_STREAMING, &s->s_flags)) {
+ struct cx18_stream *s_vbi =
+ &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
+
+ CX18_DEBUG_INFO("close stopping capture\n");
+ /* Special case: a running VBI capture for VBI insertion
+ in the mpeg stream. Need to stop that too. */
+ if (id->type == CX18_ENC_STREAM_TYPE_MPG &&
+ test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags) &&
+ !test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) {
+ CX18_DEBUG_INFO("close stopping embedded VBI capture\n");
+ cx18_stop_v4l2_encode_stream(s_vbi, 0);
+ }
+ if (id->type == CX18_ENC_STREAM_TYPE_VBI &&
+ test_bit(CX18_F_S_INTERNAL_USE, &s->s_flags))
+ /* Also used internally, don't stop capturing */
+ s->id = -1;
+ else
+ cx18_stop_v4l2_encode_stream(s, gop_end);
+ }
+ if (!gop_end) {
+ clear_bit(CX18_F_S_APPL_IO, &s->s_flags);
+ clear_bit(CX18_F_S_STREAMOFF, &s->s_flags);
+ cx18_release_stream(s);
+ }
+}
+
+int cx18_v4l2_close(struct inode *inode, struct file *filp)
+{
+ struct cx18_open_id *id = filp->private_data;
+ struct cx18 *cx = id->cx;
+ struct cx18_stream *s = &cx->streams[id->type];
+
+ CX18_DEBUG_IOCTL("close() of %s\n", s->name);
+
+ v4l2_prio_close(&cx->prio, &id->prio);
+
+ /* Easy case first: this stream was never claimed by us */
+ if (s->id != id->open_id) {
+ kfree(id);
+ return 0;
+ }
+
+ /* 'Unclaim' this stream */
+
+ /* Stop radio */
+ mutex_lock(&cx->serialize_lock);
+ if (id->type == CX18_ENC_STREAM_TYPE_RAD) {
+ /* Closing radio device, return to TV mode */
+ cx18_mute(cx);
+ /* Mark that the radio is no longer in use */
+ clear_bit(CX18_F_I_RADIO_USER, &cx->i_flags);
+ /* Switch tuner to TV */
+ cx18_call_i2c_clients(cx, VIDIOC_S_STD, &cx->std);
+ /* Select correct audio input (i.e. TV tuner or Line in) */
+ cx18_audio_set_io(cx);
+ if (atomic_read(&cx->capturing) > 0) {
+ /* Undo video mute */
+ cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2, s->handle,
+ cx->params.video_mute |
+ (cx->params.video_mute_yuv << 8));
+ }
+ /* Done! Unmute and continue. */
+ cx18_unmute(cx);
+ cx18_release_stream(s);
+ } else {
+ cx18_stop_capture(id, 0);
+ }
+ kfree(id);
+ mutex_unlock(&cx->serialize_lock);
+ return 0;
+}
+
+static int cx18_serialized_open(struct cx18_stream *s, struct file *filp)
+{
+ struct cx18 *cx = s->cx;
+ struct cx18_open_id *item;
+
+ CX18_DEBUG_FILE("open %s\n", s->name);
+
+ /* Allocate memory */
+ item = kmalloc(sizeof(struct cx18_open_id), GFP_KERNEL);
+ if (NULL == item) {
+ CX18_DEBUG_WARN("nomem on v4l2 open\n");
+ return -ENOMEM;
+ }
+ item->cx = cx;
+ item->type = s->type;
+ v4l2_prio_open(&cx->prio, &item->prio);
+
+ item->open_id = cx->open_id++;
+ filp->private_data = item;
+
+ if (item->type == CX18_ENC_STREAM_TYPE_RAD) {
+ /* Try to claim this stream */
+ if (cx18_claim_stream(item, item->type)) {
+ /* No, it's already in use */
+ kfree(item);
+ return -EBUSY;
+ }
+
+ if (!test_bit(CX18_F_I_RADIO_USER, &cx->i_flags)) {
+ if (atomic_read(&cx->capturing) > 0) {
+ /* switching to radio while capture is
+ in progress is not polite */
+ cx18_release_stream(s);
+ kfree(item);
+ return -EBUSY;
+ }
+ }
+
+ /* Mark that the radio is being used. */
+ set_bit(CX18_F_I_RADIO_USER, &cx->i_flags);
+ /* We have the radio */
+ cx18_mute(cx);
+ /* Switch tuner to radio */
+ cx18_call_i2c_clients(cx, AUDC_SET_RADIO, NULL);
+ /* Select the correct audio input (i.e. radio tuner) */
+ cx18_audio_set_io(cx);
+ /* Done! Unmute and continue. */
+ cx18_unmute(cx);
+ }
+ return 0;
+}
+
+int cx18_v4l2_open(struct inode *inode, struct file *filp)
+{
+ int res, x, y = 0;
+ struct cx18 *cx = NULL;
+ struct cx18_stream *s = NULL;
+ int minor = iminor(inode);
+
+ /* Find which card this open was on */
+ spin_lock(&cx18_cards_lock);
+ for (x = 0; cx == NULL && x < cx18_cards_active; x++) {
+ /* find out which stream this open was on */
+ for (y = 0; y < CX18_MAX_STREAMS; y++) {
+ s = &cx18_cards[x]->streams[y];
+ if (s->v4l2dev && s->v4l2dev->minor == minor) {
+ cx = cx18_cards[x];
+ break;
+ }
+ }
+ }
+ spin_unlock(&cx18_cards_lock);
+
+ if (cx == NULL) {
+ /* Couldn't find a device registered
+ on that minor, shouldn't happen! */
+ printk(KERN_WARNING "No cx18 device found on minor %d\n",
+ minor);
+ return -ENXIO;
+ }
+
+ mutex_lock(&cx->serialize_lock);
+ if (cx18_init_on_first_open(cx)) {
+ CX18_ERR("Failed to initialize on minor %d\n", minor);
+ mutex_unlock(&cx->serialize_lock);
+ return -ENXIO;
+ }
+ res = cx18_serialized_open(s, filp);
+ mutex_unlock(&cx->serialize_lock);
+ return res;
+}
+
+void cx18_mute(struct cx18 *cx)
+{
+ if (atomic_read(&cx->capturing))
+ cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2,
+ cx18_find_handle(cx), 1);
+ CX18_DEBUG_INFO("Mute\n");
+}
+
+void cx18_unmute(struct cx18 *cx)
+{
+ if (atomic_read(&cx->capturing)) {
+ cx18_msleep_timeout(100, 0);
+ cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 2,
+ cx18_find_handle(cx), 12);
+ cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2,
+ cx18_find_handle(cx), 0);
+ }
+ CX18_DEBUG_INFO("Unmute\n");
+}