// SPDX-License-Identifier: GPL-2.0-or-later 
 | 
/* 
 | 
 *  cx18 file operation functions 
 | 
 * 
 | 
 *  Derived from ivtv-fileops.c 
 | 
 * 
 | 
 *  Copyright (C) 2007  Hans Verkuil <hverkuil@xs4all.nl> 
 | 
 *  Copyright (C) 2008  Andy Walls <awalls@md.metrocast.net> 
 | 
 */ 
 | 
  
 | 
#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" 
 | 
#include <media/v4l2-event.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 and IDX 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_assoc; 
 | 
  
 | 
    /* Nothing should ever try to directly claim the IDX stream */ 
 | 
    if (type == CX18_ENC_STREAM_TYPE_IDX) { 
 | 
        CX18_WARN("MPEG Index stream cannot be claimed directly, but something tried.\n"); 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    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_ENC_STREAM_TYPE_MPG needs to claim: 
 | 
     * CX18_ENC_STREAM_TYPE_VBI, if VBI insertion is on for sliced VBI, or 
 | 
     * CX18_ENC_STREAM_TYPE_IDX, if VBI insertion is off for sliced VBI 
 | 
     * (We don't yet fix up MPEG Index entries for our inserted packets). 
 | 
     * 
 | 
     * For all other streams we're done. 
 | 
     */ 
 | 
    if (type != CX18_ENC_STREAM_TYPE_MPG) 
 | 
        return 0; 
 | 
  
 | 
    s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; 
 | 
    if (cx->vbi.insert_mpeg && !cx18_raw_vbi(cx)) 
 | 
        s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; 
 | 
    else if (!cx18_stream_enabled(s_assoc)) 
 | 
        return 0; 
 | 
  
 | 
    set_bit(CX18_F_S_CLAIMED, &s_assoc->s_flags); 
 | 
  
 | 
    /* mark that it is used internally */ 
 | 
    set_bit(CX18_F_S_INTERNAL_USE, &s_assoc->s_flags); 
 | 
    return 0; 
 | 
} 
 | 
EXPORT_SYMBOL(cx18_claim_stream); 
 | 
  
 | 
/* 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_assoc; 
 | 
  
 | 
    s->id = -1; 
 | 
    if (s->type == CX18_ENC_STREAM_TYPE_IDX) { 
 | 
        /* 
 | 
         * The IDX stream is only used internally, and can 
 | 
         * only be indirectly unclaimed by unclaiming the MPG stream. 
 | 
         */ 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    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 the 
 | 
     * CX18_ENC_STREAM_TYPE_VBI and/or CX18_ENC_STREAM_TYPE_IDX streams. 
 | 
     * 
 | 
     * For all other streams we're done. 
 | 
     */ 
 | 
    if (s->type != CX18_ENC_STREAM_TYPE_MPG) 
 | 
        return; 
 | 
  
 | 
    /* Unclaim the associated MPEG Index stream */ 
 | 
    s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; 
 | 
    if (test_and_clear_bit(CX18_F_S_INTERNAL_USE, &s_assoc->s_flags)) { 
 | 
        clear_bit(CX18_F_S_CLAIMED, &s_assoc->s_flags); 
 | 
        cx18_flush_queues(s_assoc); 
 | 
    } 
 | 
  
 | 
    /* Unclaim the associated VBI stream */ 
 | 
    s_assoc = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; 
 | 
    if (test_and_clear_bit(CX18_F_S_INTERNAL_USE, &s_assoc->s_flags)) { 
 | 
        if (s_assoc->id == -1) { 
 | 
            /* 
 | 
             * The VBI stream is not still claimed by a file 
 | 
             * descriptor, so completely unclaim it. 
 | 
             */ 
 | 
            clear_bit(CX18_F_S_CLAIMED, &s_assoc->s_flags); 
 | 
            cx18_flush_queues(s_assoc); 
 | 
        } 
 | 
    } 
 | 
} 
 | 
EXPORT_SYMBOL(cx18_release_stream); 
 | 
  
 | 
static void cx18_dualwatch(struct cx18 *cx) 
 | 
{ 
 | 
    struct v4l2_tuner vt; 
 | 
    u32 new_stereo_mode; 
 | 
    const u32 dual = 0x0200; 
 | 
  
 | 
    new_stereo_mode = v4l2_ctrl_g_ctrl(cx->cxhdl.audio_mode); 
 | 
    memset(&vt, 0, sizeof(vt)); 
 | 
    cx18_call_all(cx, tuner, 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; 
 | 
  
 | 
    CX18_DEBUG_INFO("dualwatch: change stereo flag from 0x%x to 0x%x.\n", 
 | 
               cx->dualwatch_stereo_mode, new_stereo_mode); 
 | 
    if (v4l2_ctrl_s_ctrl(cx->cxhdl.audio_mode, new_stereo_mode)) 
 | 
        CX18_DEBUG_INFO("dualwatch: changing stereo flag failed\n"); 
 | 
} 
 | 
  
 | 
  
 | 
static struct cx18_mdl *cx18_get_mdl(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_mdl *mdl; 
 | 
    DEFINE_WAIT(wait); 
 | 
  
 | 
    *err = 0; 
 | 
    while (1) { 
 | 
        if (s->type == CX18_ENC_STREAM_TYPE_MPG) { 
 | 
            /* Process pending program updates and VBI data */ 
 | 
            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 ((mdl = cx18_dequeue(s_vbi, 
 | 
                               &s_vbi->q_full))) { 
 | 
                    /* byteswap and process VBI data */ 
 | 
                    cx18_process_vbi_data(cx, mdl, 
 | 
                                  s_vbi->type); 
 | 
                    cx18_stream_put_mdl_fw(s_vbi, mdl); 
 | 
                } 
 | 
            } 
 | 
            mdl = &cx->vbi.sliced_mpeg_mdl; 
 | 
            if (mdl->readpos != mdl->bytesused) 
 | 
                return mdl; 
 | 
        } 
 | 
  
 | 
        /* do we have new data? */ 
 | 
        mdl = cx18_dequeue(s, &s->q_full); 
 | 
        if (mdl) { 
 | 
            if (!test_and_clear_bit(CX18_F_M_NEED_SWAP, 
 | 
                        &mdl->m_flags)) 
 | 
                return mdl; 
 | 
            if (s->type == CX18_ENC_STREAM_TYPE_MPG) 
 | 
                /* byteswap MPG data */ 
 | 
                cx18_mdl_swap(mdl); 
 | 
            else { 
 | 
                /* byteswap and process VBI data */ 
 | 
                cx18_process_vbi_data(cx, mdl, s->type); 
 | 
            } 
 | 
            return mdl; 
 | 
        } 
 | 
  
 | 
        /* 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 (!atomic_read(&s->q_full.depth)) 
 | 
            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_mdl(struct cx18 *cx) 
 | 
{ 
 | 
    struct cx18_mdl *mdl = &cx->vbi.sliced_mpeg_mdl; 
 | 
    struct cx18_buffer *buf = &cx->vbi.sliced_mpeg_buf; 
 | 
    int idx = cx->vbi.inserted_frame % CX18_VBI_FRAMES; 
 | 
  
 | 
    buf->buf = cx->vbi.sliced_mpeg_data[idx]; 
 | 
    buf->bytesused = cx->vbi.sliced_mpeg_size[idx]; 
 | 
    buf->readpos = 0; 
 | 
  
 | 
    mdl->curr_buf = NULL; 
 | 
    mdl->bytesused = cx->vbi.sliced_mpeg_size[idx]; 
 | 
    mdl->readpos = 0; 
 | 
} 
 | 
  
 | 
static size_t cx18_copy_buf_to_user(struct cx18_stream *s, 
 | 
    struct cx18_buffer *buf, char __user *ubuf, size_t ucount, bool *stop) 
 | 
{ 
 | 
    struct cx18 *cx = s->cx; 
 | 
    size_t len = buf->bytesused - buf->readpos; 
 | 
  
 | 
    *stop = false; 
 | 
    if (len > ucount) 
 | 
        len = ucount; 
 | 
    if (cx->vbi.insert_mpeg && s->type == CX18_ENC_STREAM_TYPE_MPG && 
 | 
        !cx18_raw_vbi(cx) && buf != &cx->vbi.sliced_mpeg_buf) { 
 | 
        /* 
 | 
         * Try to find a good splice point in the PS, just before 
 | 
         * an MPEG-2 Program Pack start code, and provide only 
 | 
         * up to that point to the user, so it's easy to insert VBI data 
 | 
         * the next time around. 
 | 
         * 
 | 
         * This will not work for an MPEG-2 TS and has only been 
 | 
         * verified by analysis to work for an MPEG-2 PS.  Helen Buus 
 | 
         * pointed out this works for the CX23416 MPEG-2 DVD compatible 
 | 
         * stream, and research indicates both the MPEG 2 SVCD and DVD 
 | 
         * stream types use an MPEG-2 PS container. 
 | 
         */ 
 | 
        /* 
 | 
         * An MPEG-2 Program Stream (PS) is a series of 
 | 
         * MPEG-2 Program Packs terminated by an 
 | 
         * MPEG Program End Code after the last Program Pack. 
 | 
         * A Program Pack may hold a PS System Header packet and any 
 | 
         * number of Program Elementary Stream (PES) Packets 
 | 
         */ 
 | 
        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) { 
 | 
            /* Scan for a 0 to find a potential MPEG-2 start code */ 
 | 
            q = memchr(p, 0, start + len - p); 
 | 
            if (q == NULL) 
 | 
                break; 
 | 
            p = q + 1; 
 | 
            /* 
 | 
             * Keep looking if not a 
 | 
             * MPEG-2 Pack header start code:  0x00 0x00 0x01 0xba 
 | 
             * or MPEG-2 video PES start code: 0x00 0x00 0x01 0xe0 
 | 
             */ 
 | 
            if ((char *)q + 15 >= buf->buf + buf->bytesused || 
 | 
                q[1] != 0 || q[2] != 1 || q[3] != ch) 
 | 
                continue; 
 | 
  
 | 
            /* If expecting the primary video PES */ 
 | 
            if (!cx->search_pack_header) { 
 | 
                /* Continue if it couldn't be a PES packet */ 
 | 
                if ((q[6] & 0xc0) != 0x80) 
 | 
                    continue; 
 | 
                /* Check if a PTS or PTS & DTS follow */ 
 | 
                if (((q[7] & 0xc0) == 0x80 &&  /* PTS only */ 
 | 
                     (q[9] & 0xf0) == 0x20) || /* PTS only */ 
 | 
                    ((q[7] & 0xc0) == 0xc0 &&  /* PTS & DTS */ 
 | 
                     (q[9] & 0xf0) == 0x30)) { /* DTS follows */ 
 | 
                    /* Assume we found the video PES hdr */ 
 | 
                    ch = 0xba; /* next want a Program Pack*/ 
 | 
                    cx->search_pack_header = 1; 
 | 
                    p = q + 9; /* Skip this video PES hdr */ 
 | 
                } 
 | 
                continue; 
 | 
            } 
 | 
  
 | 
            /* We may have found a Program Pack start code */ 
 | 
  
 | 
            /* Get the count of stuffing bytes & verify them */ 
 | 
            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 && /* right number of stuffing bytes*/ 
 | 
                (q[4] & 0xc4) == 0x44 && /* marker check */ 
 | 
                (q[12] & 3) == 3 &&  /* marker check */ 
 | 
                q[14 + stuffing] == 0 && /* PES Pack or Sys Hdr */ 
 | 
                q[15 + stuffing] == 0 && 
 | 
                q[16 + stuffing] == 1) { 
 | 
                /* We declare we actually found a Program Pack*/ 
 | 
                cx->search_pack_header = 0; /* expect vid PES */ 
 | 
                len = (char *)q - start; 
 | 
                cx18_setup_sliced_vbi_mdl(cx); 
 | 
                *stop = true; 
 | 
                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 size_t cx18_copy_mdl_to_user(struct cx18_stream *s, 
 | 
        struct cx18_mdl *mdl, char __user *ubuf, size_t ucount) 
 | 
{ 
 | 
    size_t tot_written = 0; 
 | 
    int rc; 
 | 
    bool stop = false; 
 | 
  
 | 
    if (mdl->curr_buf == NULL) 
 | 
        mdl->curr_buf = list_first_entry(&mdl->buf_list, 
 | 
                         struct cx18_buffer, list); 
 | 
  
 | 
    if (list_entry_is_past_end(mdl->curr_buf, &mdl->buf_list, list)) { 
 | 
        /* 
 | 
         * For some reason we've exhausted the buffers, but the MDL 
 | 
         * object still said some data was unread. 
 | 
         * Fix that and bail out. 
 | 
         */ 
 | 
        mdl->readpos = mdl->bytesused; 
 | 
        return 0; 
 | 
    } 
 | 
  
 | 
    list_for_each_entry_from(mdl->curr_buf, &mdl->buf_list, list) { 
 | 
  
 | 
        if (mdl->curr_buf->readpos >= mdl->curr_buf->bytesused) 
 | 
            continue; 
 | 
  
 | 
        rc = cx18_copy_buf_to_user(s, mdl->curr_buf, ubuf + tot_written, 
 | 
                       ucount - tot_written, &stop); 
 | 
        if (rc < 0) 
 | 
            return rc; 
 | 
        mdl->readpos += rc; 
 | 
        tot_written += rc; 
 | 
  
 | 
        if (stop ||    /* Forced stopping point for VBI insertion */ 
 | 
            tot_written >= ucount ||    /* Reader request satisfied */ 
 | 
            mdl->curr_buf->readpos < mdl->curr_buf->bytesused || 
 | 
            mdl->readpos >= mdl->bytesused) /* MDL buffers drained */ 
 | 
            break; 
 | 
    } 
 | 
    return tot_written; 
 | 
} 
 | 
  
 | 
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->ana_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 && !cx18_raw_vbi(cx)) 
 | 
        single_frame = 1; 
 | 
  
 | 
    for (;;) { 
 | 
        struct cx18_mdl *mdl; 
 | 
        int rc; 
 | 
  
 | 
        mdl = cx18_get_mdl(s, non_block, &rc); 
 | 
        /* if there is no data available... */ 
 | 
        if (mdl == 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_mdl_to_user(s, mdl, ubuf + tot_written, 
 | 
                tot_count - tot_written); 
 | 
  
 | 
        if (mdl != &cx->vbi.sliced_mpeg_mdl) { 
 | 
            if (mdl->readpos == mdl->bytesused) 
 | 
                cx18_stream_put_mdl_fw(s, mdl); 
 | 
            else 
 | 
                cx18_push(s, mdl, &s->q_full); 
 | 
        } else if (mdl->readpos == mdl->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 += mdl->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; 
 | 
    struct cx18_stream *s_idx; 
 | 
  
 | 
    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 associated VBI or IDX stream capture if required */ 
 | 
    s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; 
 | 
    s_idx = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; 
 | 
    if (s->type == CX18_ENC_STREAM_TYPE_MPG) { 
 | 
        /* 
 | 
         * The VBI and IDX streams should have been claimed 
 | 
         * automatically, if for internal use, when the MPG stream was 
 | 
         * claimed.  We only need to start these streams capturing. 
 | 
         */ 
 | 
        if (test_bit(CX18_F_S_INTERNAL_USE, &s_idx->s_flags) && 
 | 
            !test_and_set_bit(CX18_F_S_STREAMING, &s_idx->s_flags)) { 
 | 
            if (cx18_start_v4l2_encode_stream(s_idx)) { 
 | 
                CX18_DEBUG_WARN("IDX capture start failed\n"); 
 | 
                clear_bit(CX18_F_S_STREAMING, &s_idx->s_flags); 
 | 
                goto start_failed; 
 | 
            } 
 | 
            CX18_DEBUG_INFO("IDX capture started\n"); 
 | 
        } 
 | 
        if (test_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags) && 
 | 
            !test_and_set_bit(CX18_F_S_STREAMING, &s_vbi->s_flags)) { 
 | 
            if (cx18_start_v4l2_encode_stream(s_vbi)) { 
 | 
                CX18_DEBUG_WARN("VBI capture start failed\n"); 
 | 
                clear_bit(CX18_F_S_STREAMING, &s_vbi->s_flags); 
 | 
                goto start_failed; 
 | 
            } 
 | 
            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; 
 | 
    } 
 | 
  
 | 
start_failed: 
 | 
    CX18_DEBUG_WARN("Failed to start capturing for stream %s\n", s->name); 
 | 
  
 | 
    /* 
 | 
     * The associated VBI and IDX streams for internal use are released 
 | 
     * automatically when the MPG stream is released.  We only need to stop 
 | 
     * the associated stream. 
 | 
     */ 
 | 
    if (s->type == CX18_ENC_STREAM_TYPE_MPG) { 
 | 
        /* Stop the IDX stream which is always for internal use */ 
 | 
        if (test_bit(CX18_F_S_STREAMING, &s_idx->s_flags)) { 
 | 
            cx18_stop_v4l2_encode_stream(s_idx, 0); 
 | 
            clear_bit(CX18_F_S_STREAMING, &s_idx->s_flags); 
 | 
        } 
 | 
        /* Stop the VBI stream, if only running for internal use */ 
 | 
        if (test_bit(CX18_F_S_STREAMING, &s_vbi->s_flags) && 
 | 
            !test_bit(CX18_F_S_APPL_IO, &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); /* Also releases associated streams */ 
 | 
    return -EIO; 
 | 
} 
 | 
  
 | 
ssize_t cx18_v4l2_read(struct file *filp, char __user *buf, size_t count, 
 | 
        loff_t *pos) 
 | 
{ 
 | 
    struct cx18_open_id *id = file2id(filp); 
 | 
    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; 
 | 
  
 | 
    if ((s->vb_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) && 
 | 
        (id->type == CX18_ENC_STREAM_TYPE_YUV)) { 
 | 
        return videobuf_read_stream(&s->vbuf_q, buf, count, pos, 0, 
 | 
            filp->f_flags & O_NONBLOCK); 
 | 
    } 
 | 
  
 | 
    return cx18_read_pos(s, buf, count, pos, filp->f_flags & O_NONBLOCK); 
 | 
} 
 | 
  
 | 
__poll_t cx18_v4l2_enc_poll(struct file *filp, poll_table *wait) 
 | 
{ 
 | 
    __poll_t req_events = poll_requested_events(wait); 
 | 
    struct cx18_open_id *id = file2id(filp); 
 | 
    struct cx18 *cx = id->cx; 
 | 
    struct cx18_stream *s = &cx->streams[id->type]; 
 | 
    int eof = test_bit(CX18_F_S_STREAMOFF, &s->s_flags); 
 | 
    __poll_t res = 0; 
 | 
  
 | 
    /* Start a capture if there is none */ 
 | 
    if (!eof && !test_bit(CX18_F_S_STREAMING, &s->s_flags) && 
 | 
            (req_events & (EPOLLIN | EPOLLRDNORM))) { 
 | 
        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 EPOLLERR; 
 | 
        } 
 | 
        CX18_DEBUG_FILE("Encoder poll started capture\n"); 
 | 
    } 
 | 
  
 | 
    if ((s->vb_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) && 
 | 
        (id->type == CX18_ENC_STREAM_TYPE_YUV)) { 
 | 
        __poll_t videobuf_poll = videobuf_poll_stream(filp, &s->vbuf_q, wait); 
 | 
  
 | 
        if (v4l2_event_pending(&id->fh)) 
 | 
            res |= EPOLLPRI; 
 | 
        if (eof && videobuf_poll == EPOLLERR) 
 | 
            return res | EPOLLHUP; 
 | 
        return res | videobuf_poll; 
 | 
    } 
 | 
  
 | 
    /* add stream's waitq to the poll list */ 
 | 
    CX18_DEBUG_HI_FILE("Encoder poll\n"); 
 | 
    if (v4l2_event_pending(&id->fh)) 
 | 
        res |= EPOLLPRI; 
 | 
    else 
 | 
        poll_wait(filp, &s->waitq, wait); 
 | 
  
 | 
    if (atomic_read(&s->q_full.depth)) 
 | 
        return res | EPOLLIN | EPOLLRDNORM; 
 | 
    if (eof) 
 | 
        return res | EPOLLHUP; 
 | 
    return res; 
 | 
} 
 | 
  
 | 
int cx18_v4l2_mmap(struct file *file, struct vm_area_struct *vma) 
 | 
{ 
 | 
    struct cx18_open_id *id = file->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); 
 | 
  
 | 
    if ((s->vb_type == V4L2_BUF_TYPE_VIDEO_CAPTURE) && 
 | 
        (id->type == CX18_ENC_STREAM_TYPE_YUV)) { 
 | 
  
 | 
        /* 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 -EINVAL; 
 | 
            } 
 | 
            CX18_DEBUG_FILE("Encoder mmap started capture\n"); 
 | 
        } 
 | 
  
 | 
        return videobuf_mmap_mapper(&s->vbuf_q, vma); 
 | 
    } 
 | 
  
 | 
    return -EINVAL; 
 | 
} 
 | 
  
 | 
void cx18_vb_timeout(struct timer_list *t) 
 | 
{ 
 | 
    struct cx18_stream *s = from_timer(s, t, vb_timeout); 
 | 
    struct cx18_videobuf_buffer *buf; 
 | 
    unsigned long flags; 
 | 
  
 | 
    /* Return all of the buffers in error state, so the vbi/vid inode 
 | 
     * can return from blocking. 
 | 
     */ 
 | 
    spin_lock_irqsave(&s->vb_lock, flags); 
 | 
    while (!list_empty(&s->vb_capture)) { 
 | 
        buf = list_entry(s->vb_capture.next, 
 | 
            struct cx18_videobuf_buffer, vb.queue); 
 | 
        list_del(&buf->vb.queue); 
 | 
        buf->vb.state = VIDEOBUF_ERROR; 
 | 
        wake_up(&buf->vb.done); 
 | 
    } 
 | 
    spin_unlock_irqrestore(&s->vb_lock, flags); 
 | 
} 
 | 
  
 | 
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]; 
 | 
    struct cx18_stream *s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI]; 
 | 
    struct cx18_stream *s_idx = &cx->streams[CX18_ENC_STREAM_TYPE_IDX]; 
 | 
  
 | 
    CX18_DEBUG_IOCTL("close() of %s\n", s->name); 
 | 
  
 | 
    /* 'Unclaim' this stream */ 
 | 
  
 | 
    /* Stop capturing */ 
 | 
    if (test_bit(CX18_F_S_STREAMING, &s->s_flags)) { 
 | 
        CX18_DEBUG_INFO("close stopping capture\n"); 
 | 
        if (id->type == CX18_ENC_STREAM_TYPE_MPG) { 
 | 
            /* Stop internal use associated VBI and IDX streams */ 
 | 
            if (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 (test_bit(CX18_F_S_STREAMING, &s_idx->s_flags)) { 
 | 
                CX18_DEBUG_INFO("close stopping IDX capture\n"); 
 | 
                cx18_stop_v4l2_encode_stream(s_idx, 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 file *filp) 
 | 
{ 
 | 
    struct v4l2_fh *fh = filp->private_data; 
 | 
    struct cx18_open_id *id = fh2id(fh); 
 | 
    struct cx18 *cx = id->cx; 
 | 
    struct cx18_stream *s = &cx->streams[id->type]; 
 | 
  
 | 
    CX18_DEBUG_IOCTL("close() of %s\n", s->name); 
 | 
  
 | 
    mutex_lock(&cx->serialize_lock); 
 | 
    /* Stop radio */ 
 | 
    if (id->type == CX18_ENC_STREAM_TYPE_RAD && 
 | 
            v4l2_fh_is_singular_file(filp)) { 
 | 
        /* 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_all(cx, video, s_std, cx->std); 
 | 
        /* Select correct audio input (i.e. TV tuner or Line in) */ 
 | 
        cx18_audio_set_io(cx); 
 | 
        if (atomic_read(&cx->ana_capturing) > 0) { 
 | 
            /* Undo video mute */ 
 | 
            cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2, s->handle, 
 | 
                (v4l2_ctrl_g_ctrl(cx->cxhdl.video_mute) | 
 | 
                (v4l2_ctrl_g_ctrl(cx->cxhdl.video_mute_yuv) << 8))); 
 | 
        } 
 | 
        /* Done! Unmute and continue. */ 
 | 
        cx18_unmute(cx); 
 | 
    } 
 | 
  
 | 
    v4l2_fh_del(fh); 
 | 
    v4l2_fh_exit(fh); 
 | 
  
 | 
    /* 'Unclaim' this stream */ 
 | 
    if (s->id == id->open_id) 
 | 
        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 = kzalloc(sizeof(struct cx18_open_id), GFP_KERNEL); 
 | 
    if (NULL == item) { 
 | 
        CX18_DEBUG_WARN("nomem on v4l2 open\n"); 
 | 
        return -ENOMEM; 
 | 
    } 
 | 
    v4l2_fh_init(&item->fh, &s->video_dev); 
 | 
  
 | 
    item->cx = cx; 
 | 
    item->type = s->type; 
 | 
  
 | 
    item->open_id = cx->open_id++; 
 | 
    filp->private_data = &item->fh; 
 | 
    v4l2_fh_add(&item->fh); 
 | 
  
 | 
    if (item->type == CX18_ENC_STREAM_TYPE_RAD && 
 | 
            v4l2_fh_is_singular_file(filp)) { 
 | 
        if (!test_bit(CX18_F_I_RADIO_USER, &cx->i_flags)) { 
 | 
            if (atomic_read(&cx->ana_capturing) > 0) { 
 | 
                /* switching to radio while capture is 
 | 
                   in progress is not polite */ 
 | 
                v4l2_fh_del(&item->fh); 
 | 
                v4l2_fh_exit(&item->fh); 
 | 
                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_all(cx, tuner, s_radio); 
 | 
        /* 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 file *filp) 
 | 
{ 
 | 
    int res; 
 | 
    struct video_device *video_dev = video_devdata(filp); 
 | 
    struct cx18_stream *s = video_get_drvdata(video_dev); 
 | 
    struct cx18 *cx = s->cx; 
 | 
  
 | 
    mutex_lock(&cx->serialize_lock); 
 | 
    if (cx18_init_on_first_open(cx)) { 
 | 
        CX18_ERR("Failed to initialize on %s\n", 
 | 
             video_device_node_name(video_dev)); 
 | 
        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) 
 | 
{ 
 | 
    u32 h; 
 | 
    if (atomic_read(&cx->ana_capturing)) { 
 | 
        h = cx18_find_handle(cx); 
 | 
        if (h != CX18_INVALID_TASK_HANDLE) 
 | 
            cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2, h, 1); 
 | 
        else 
 | 
            CX18_ERR("Can't find valid task handle for mute\n"); 
 | 
    } 
 | 
    CX18_DEBUG_INFO("Mute\n"); 
 | 
} 
 | 
  
 | 
void cx18_unmute(struct cx18 *cx) 
 | 
{ 
 | 
    u32 h; 
 | 
    if (atomic_read(&cx->ana_capturing)) { 
 | 
        h = cx18_find_handle(cx); 
 | 
        if (h != CX18_INVALID_TASK_HANDLE) { 
 | 
            cx18_msleep_timeout(100, 0); 
 | 
            cx18_vapi(cx, CX18_CPU_SET_MISC_PARAMETERS, 2, h, 12); 
 | 
            cx18_vapi(cx, CX18_CPU_SET_AUDIO_MUTE, 2, h, 0); 
 | 
        } else 
 | 
            CX18_ERR("Can't find valid task handle for unmute\n"); 
 | 
    } 
 | 
    CX18_DEBUG_INFO("Unmute\n"); 
 | 
} 
 |