Subject: [PATCH 1/7] VPFE capture driver for DM355 and DM6446 From: m-karicheri2-l0cyMroinI0@public.gmane.org Date: Fri, 13 Mar 2009 17:15:31 -0400 To: davinci-linux-open-source-VycZQUHpC/PFrsHnngEfi1aTQe2KTcn/@public.gmane.org, davinci_opensource_ccb-uAqBSO/uNfhBDgjK7y7TUQ@public.gmane.org, psp_video-uAqBSO/uNfhBDgjK7y7TUQ@public.gmane.org Newsgroups: gmane.linux.davinci This patch is for the vpfe capture driver for DM355 & DM6446 from Texas instruments. VPFE stands for Video Processing Front End which is the basic IP on DMxxx family for video capture and processing. vpfe capture driver is a v4l2 bridge driver developed based on v4l2-int-device model. It interfaces slave decoder devices to the bridge driver using this model. V4L2 community has already developed a v4l2 sub device model for this purpose. But at this time, tvp514x, the only slave decoder that can work with DM355 and DM6446 VPFE, is using the v4l2-int-device model. So decision is taken to first use this model to submit the driver to the community and plan for a migration to sub device model when tvp514x driver become sub device compliant. The driver uses ccdc_hw_device interface to configure CCDC based on the interface requirement of the slave decoder device. This driver is integrated with the tvp514x driver available in open source kernel. The driver is tested using a loopback application (Will be made available upon request) that captures video frames from the capture driver and display it at the output of VPBE using the FBDev video output device. Signed-off-by: Murali Karicheri --- drivers/media/video/davinci/vpfe_capture.c | 2248 ++++++++++++++++++++++++++++ drivers/media/video/davinci_vpfe.c | 1136 -------------- include/media/davinci/vpfe_capture.h | 272 ++++ include/media/davinci/vpfe_types.h | 71 + include/media/davinci_vpfe.h | 121 -- 5 files changed, 2591 insertions(+), 1257 deletions(-) create mode 100644 drivers/media/video/davinci/vpfe_capture.c delete mode 100644 drivers/media/video/davinci_vpfe.c create mode 100644 include/media/davinci/vpfe_capture.h create mode 100644 include/media/davinci/vpfe_types.h delete mode 100644 include/media/davinci_vpfe.h diff --git a/drivers/media/video/davinci/vpfe_capture.c b/drivers/media/video/davinci/vpfe_capture.c new file mode 100644 index 0000000..decbffc --- /dev/null +++ b/drivers/media/video/davinci/vpfe_capture.c @@ -0,0 +1,2248 @@ +/* + * Copyright (C) 2008-2009 Texas Instruments Inc + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +static int debug; +static char *ch0_decoder = "TVP514X"; +static u32 ch0_numbuffers = 3; +static u32 ch0_bufsize = (720 * 576 * 2); +module_param(ch0_decoder, charp, S_IRUGO); +module_param(ch0_numbuffers, uint, S_IRUGO); +module_param(ch0_bufsize, uint, S_IRUGO); +module_param(debug, int, 0); + +static struct vpfe_config_params config_params = { + .min_numbuffers = 3, + .numbuffers[0] = 3, + .min_bufsize[0] = 720 * 480 * 2, + .channel_bufsize[0] = 720 * 576 * 2, +}; + +static int vpfe_nr[] = { 0 }; + +static struct vpfe_device vpfe_obj = { {NULL} }; +static struct device *vpfe_dev; + +static struct v4l2_capability vpfe_videocap = { + .driver = CAPTURE_DRV_NAME, + .card = "DaVinci EVM", + .bus_info = "Platform", + .version = VPFE_CAPTURE_VERSION_CODE, + .capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING +}; + +#define VPFE_PIXELASPECT_NTSC {11, 10} +#define VPFE_PIXELASPECT_PAL {54, 59} + +/* standard information */ +struct vpfe_standard { + v4l2_std_id std_id; + unsigned int width; + unsigned int height; + struct v4l2_fract pixelaspect; + /* 0 - progressive, 1 - interlaced */ + char frame_format; +}; + +struct vpfe_standard vpfe_standards[] = { + {V4L2_STD_NTSC, 720, 480, VPFE_PIXELASPECT_NTSC, 1}, + {V4L2_STD_PAL, 720, 576, VPFE_PIXELASPECT_PAL, 1}, +}; + +static int vpfe_max_standards = ARRAY_SIZE(vpfe_standards); + +/* Used when raw Bayer image from ccdc is directly captured to SDRAM */ +static struct vpfe_pixel_format + vpfe_pix_fmts[VPFE_MAX_PIX_FORMATS] = { + { + .pix_fmt = V4L2_PIX_FMT_SBGGR8, + .desc = "Raw Bayer GrRBGb 8bit A-Law compressed", + .hw_fmt = VPFE_BAYER_8BIT_PACK_ALAW, + }, + { + .pix_fmt = V4L2_PIX_FMT_SBGGR16, + .desc = "Raw Bayer GrRBGb - 16bit", + .hw_fmt = VPFE_BAYER, + }, + { + .pix_fmt = V4L2_PIX_FMT_SGRBG10DPCM8, + .desc = "Raw Bayer GrRBGb 8 bit DPCM compressed", + .hw_fmt = VPFE_BAYER_8BIT_PACK_DPCM, + }, + { + .pix_fmt = V4L2_PIX_FMT_UYVY, + .desc = "YCbCr 4:2:2 Interleaved UYVY", + .hw_fmt = VPFE_UYVY, + }, + { + .pix_fmt = V4L2_PIX_FMT_YUYV, + .desc = "YCbCr 4:2:2 Interleaved YUYV", + .hw_fmt = VPFE_YUYV, + }, + { + .pix_fmt = V4L2_PIX_FMT_NV12, + .desc = "Y/CbCr 4:2:0 - Semi planar", + .hw_fmt = VPFE_YUV420, + }, +}; + + +static int vpfe_lookup_hw_format(u32 pix_format) +{ + int i, ret = -EINVAL; + + for (i = 0; i < VPFE_MAX_PIX_FORMATS; i++) { + if (pix_format == vpfe_pix_fmts[i].pix_fmt) { + ret = i; + break; + } + } + return ret; +} + +static int vpfe_lookup_v4l2_pix_format(enum vpfe_hw_pix_format hw_pix) +{ + int i, ret = -EINVAL; + + for (i = 0; i < VPFE_MAX_PIX_FORMATS; i++) { + if (hw_pix == vpfe_pix_fmts[i].hw_fmt) { + ret = i; + break; + } + } + return ret; +} + + +/* Used when raw YUV image from ccdc is directly captured to SDRAM */ +static void vpfe_slave_device_unregister(struct v4l2_int_device *s) +{ + int index; + struct channel_obj *channel = s->u.slave->master->priv; + + for (index = 0; index < VPFE_CAPTURE_NUM_DECODERS; index++) { + if ((channel->decoder[index] == s) + && (index == channel->current_decoder)) { + if (channel->common->started) { + /* Streaming is ON. So return busy */ + v4l2_err(vpfe_dev->driver, + "Steaming ON. Cannot unregister" + "decoder %s\n", s->name); + return; + } else { + channel->decoder[index] = NULL; + channel->numdecoders--; + break; + } + } + } + if (index == VPFE_CAPTURE_NUM_DECODERS) + v4l2_err(vpfe_dev->driver, + "No matching decoder registered" + "decoder %s\n", s->name); +} + +static int vpfe_get_stdinfo(struct channel_obj *ch, v4l2_std_id *std_id) +{ + int i; + struct video_obj *vid_ch = NULL; + + vid_ch = &(ch->video); + + for (i = 0; i < vpfe_max_standards; i++) { + if (vpfe_standards[i].std_id == *std_id) { + vid_ch->std_info.activepixels = + vpfe_standards[i].width; + vid_ch->std_info.activelines = + vpfe_standards[i].height; + vid_ch->std_info.frame_format = + vpfe_standards[i].frame_format; + vid_ch->index = i; + break; + } + } + if (i == vpfe_max_standards) { + v4l2_err(vpfe_dev->driver, "standard not supported\n"); + return -EFAULT; + } + return 0; +} + +/* vpfe_device_register: Used for registering a slave decoder + * device with master + */ +static int vpfe_slave_device_register(struct v4l2_int_device *s) +{ + struct channel_obj *channel = s->u.slave->master->priv; + struct common_obj *common = &channel->common[VPFE_VIDEO_INDEX]; + int err = 0, index; + dev_notice(vpfe_dev, "register slave %s \n", s->name); + if (ISNULL(channel)) + return -EINVAL; + + if (!channel->numdecoders) { + if (!vidioc_int_dev_init(s)) { + channel->current_decoder = 0; + channel->decoder[channel->current_decoder] = s; + v4l2_info(vpfe_dev->driver, "Current decoder is set to" + " %s\n", (s->name)); + } + } else { + /* search through the array for an empty entry */ + for (index = 0; index < VPFE_CAPTURE_NUM_DECODERS; index++) { + if (ISNULL(channel->decoder[index])) { + channel->decoder[index] = s; + break; + } + } + if (index == VPFE_CAPTURE_NUM_DECODERS) { + v4l2_err(vpfe_dev->driver, + "decoder count reached" + " maximum allowed\n"); + return -ENOMEM; + } + if (!strncmp(ch0_decoder, s->name, strlen(ch0_decoder))) { + if (!common->started) { + if (!vidioc_int_dev_init(s)) { + channel->current_decoder = index; + v4l2_info(vpfe_dev->driver, + "Current decoder is" + " set to %s\n", (s->name)); + } + } + } + } + channel->numdecoders++; + return err; +} + +/* vpfe capture master. All slave decoders registers + * with master using vpfe_device_register and deregisters + * using vpfe_slave_device_unregister + */ +static struct v4l2_int_master vpfe_master = { + .attach = vpfe_slave_device_register, + .detach = vpfe_slave_device_unregister, +}; + +static struct v4l2_int_device vpfe_capture = { + .module = THIS_MODULE, + .name = CAPTURE_DRV_NAME, + .type = v4l2_int_type_master, + .u = { + .master = &vpfe_master + }, +}; + +/* Call this after storing ifparams in channel block */ +static int vpfe_set_hw_if_type(struct channel_obj *channel) +{ + struct vpfe_capture_input *input = channel->video.input; + + switch (input->inputs[input->current_input].route.output) { + case OUTPUT_10BIT_422_EMBEDDED_SYNC: + channel->vpfe_if = VPFE_BT656; + break; + case OUTPUT_20BIT_422_SEPERATE_SYNC: + channel->vpfe_if = VPFE_YCBCR_SYNC_16; + break; + case OUTPUT_10BIT_422_SEPERATE_SYNC: + channel->vpfe_if = VPFE_YCBCR_SYNC_8; + default: + v4l2_err(vpfe_dev->driver, "decoder output" + " not supported, %d\n", + input->inputs[input->current_input].route.output); + return -EFAULT; + } + return ccdc_hw_dev.set_hw_if_type(channel->vpfe_if); +} + +static int vpfe_get_image_format(struct v4l2_format *f) +{ + struct v4l2_rect image_win; + enum ccdc_buftype buf_type; + enum ccdc_frmfmt frm_fmt; + enum vpfe_hw_pix_format hw_pix; + int ret = 0; + + memset(f, 0, sizeof(struct v4l2_format)); + f->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + ccdc_hw_dev.get_image_window(&image_win); + f->fmt.pix.width = image_win.width; + f->fmt.pix.height = image_win.height; + ccdc_hw_dev.get_line_length(&f->fmt.pix.bytesperline); + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * + f->fmt.pix.height; + ccdc_hw_dev.get_buftype(&buf_type); + ccdc_hw_dev.get_pixelformat(&hw_pix); + + if (hw_pix == VPFE_BAYER) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_SBGGR16; + else if (hw_pix == VPFE_BAYER_8BIT_PACK_ALAW) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_SBGGR8; + else if (hw_pix == VPFE_UYVY) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; + else if (hw_pix == VPFE_YUYV) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + else { + v4l2_err(vpfe_dev->driver, "Invalid HW pix format detected"); + ret = -EINVAL; + goto out; + } + ccdc_hw_dev.get_frame_format(&frm_fmt); + if (frm_fmt == CCDC_FRMFMT_PROGRESSIVE) + f->fmt.pix.field = V4L2_FIELD_NONE; + else if (frm_fmt == CCDC_FRMFMT_INTERLACED) { + if (buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED) + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + else if (buf_type == CCDC_BUFTYPE_FLD_SEPARATED) + f->fmt.pix.field = V4L2_FIELD_SEQ_TB; + else + ret = -EINVAL; + } else + ret = -EINVAL; +out: + return ret; +} + +/* vpfe_config_default_format: Update format information */ +static int vpfe_config_default_format(struct channel_obj *ch) +{ + struct common_obj *common = &(ch->common[VPFE_VIDEO_INDEX]); + struct v4l2_int_device *dec = ch->decoder[ch->current_decoder]; + struct v4l2_rect win; + int err = 0; + struct video_obj *vid_ch = NULL; + + vid_ch = &(ch->video); + common->crop.top = 0; + common->crop.left = 0; + /* first get format information from the decoder. + * if not available, get it from CCDC + */ + if ((vidioc_int_g_fmt_cap(dec, &common->fmt)) < 0) + vpfe_get_image_format(&common->fmt); + else { + /* set up all parameters in CCDC */ + win.top = common->crop.top; + win.left = common->crop.left; + win.width = common->fmt.fmt.pix.width; + win.height = common->fmt.fmt.pix.height; + ccdc_hw_dev.set_image_window(&win); + if (common->fmt.fmt.pix.field == + V4L2_FIELD_INTERLACED) { + err |= + ccdc_hw_dev.set_buftype(CCDC_BUFTYPE_FLD_INTERLEAVED); + err |= + ccdc_hw_dev.set_frame_format(CCDC_FRMFMT_INTERLACED); + } else if (common->fmt.fmt.pix.field == + V4L2_FIELD_SEQ_TB) { + err |= + ccdc_hw_dev.set_buftype(CCDC_BUFTYPE_FLD_SEPARATED); + err |= + ccdc_hw_dev.set_frame_format(CCDC_FRMFMT_INTERLACED); + } else if (common->fmt.fmt.pix.field == + V4L2_FIELD_NONE) { + err |= + ccdc_hw_dev.set_frame_format(CCDC_FRMFMT_PROGRESSIVE); + } else { + v4l2_dbg(1, debug, vpfe_dev->driver, + "\n Decoder field not supported!"); + err = -EINVAL; + goto out; + } + } + /* set the crop limits */ + vid_ch->std_info.activepixels = common->fmt.fmt.pix.width; + vid_ch->std_info.activelines = common->fmt.fmt.pix.height; + if (config_params.numbuffers[ch->channel_id] == 0) + common->memory = V4L2_MEMORY_USERPTR; + else + common->memory = V4L2_MEMORY_MMAP; +out: + return err; +} + +static int vpfe_initialize_channel(struct channel_obj *channel, + struct v4l2_int_device *dec) +{ + struct common_obj *common = NULL; + struct video_obj *vid_ch = NULL; + int err = 0; + + common = &(channel->common[VPFE_VIDEO_INDEX]); + vid_ch = &(channel->video); + channel->out_from = VPFE_CCDC_OUT; + vid_ch->input->current_input = 0; + + err = vidioc_int_g_ifparm(dec, &channel->ifparams); + if (err) { + v4l2_err(vpfe_dev->driver, + "vidioc_int_g_ifparm failed with %d\n", err); + return err; + } + + err = vpfe_set_hw_if_type(channel); + if (err) + return err; + + /* Initialize decoder by calling initialize function */ + err = vidioc_int_s_power(dec, 1); + if (err) { + v4l2_err(vpfe_dev->driver, + "unable to power on the decoder, %s, error %d\n", + dec->name, + err); + return err; + } + + err = vidioc_int_init(dec); + if (err) { + v4l2_err(vpfe_dev->driver, + "cannot initialize decoder - error %d\n", + err); + return err; + } + + /* Configure the default format information */ + err = vpfe_config_default_format(channel); + + /* now open the ccdc device to initialize it */ + ccdc_hw_dev.open(vpfe_dev); + channel->initialized = 1; + return err; +} + +/* vpfe_open : It creates object of file handle structure and + * stores it in private_data member of filepointer + */ +static int vpfe_open(struct file *filep) +{ + int minor = iminor(filep->f_path.dentry->d_inode); + struct channel_obj *channel = NULL; + struct v4l2_int_device *dec = NULL; + struct common_obj *common = NULL; + struct vpfe_fh *fh = NULL; + + v4l2_dbg(1, debug, vpfe_dev->driver, "vpfe_open\n"); + + /* Check for valid minor number */ + channel = vpfe_obj.dev[0]; + common = &(channel->common[VPFE_VIDEO_INDEX]); + if (minor != channel->video_dev->minor) { + v4l2_err(vpfe_dev->driver, "device not found\n"); + return -ENODEV; + } + + if (!channel->numdecoders) { + v4l2_err(vpfe_dev->driver, "No decoder registered\n"); + return -ENODEV; + } + + dec = channel->decoder[channel->current_decoder]; + + /* Allocate memory for the file handle object */ + fh = kmalloc(sizeof(struct vpfe_fh), GFP_KERNEL); + if (ISNULL(fh)) { + v4l2_err(vpfe_dev->driver, + "unable to allocate memory for file handle object\n"); + return -ENOMEM; + } + /* store pointer to fh in private_data member of filep */ + filep->private_data = fh; + fh->channel = channel; + fh->initialized = 0; + /* If decoder is not initialized. initialize it */ + if (!channel->initialized) { + if (vpfe_initialize_channel(channel, dec)) + return -ENODEV; + fh->initialized = 1; + } + /* Increment channel usrs counter */ + channel->usrs++; + /* Set io_allowed member to false */ + fh->io_allowed[VPFE_VIDEO_INDEX] = 0; + /* Initialize priority of this instance to default priority */ + fh->prio = V4L2_PRIORITY_UNSET; + v4l2_prio_open(&channel->prio, &fh->prio); + + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); + return 0; +} + +/*ISR for VINT0*/ +static irqreturn_t vpfe_isr(int irq, void *dev_id) +{ + struct timeval timevalue; + struct channel_obj *channel = NULL; + struct common_obj *common = NULL; + struct video_obj *vid_ch = NULL; + struct vpfe_device *dev = dev_id; + unsigned long addr; + int fid; + enum v4l2_field field; + channel = dev->dev[VPFE_CHANNEL0_VIDEO]; + common = &(channel->common[VPFE_VIDEO_INDEX]); + vid_ch = &(channel->video); + field = common->fmt.fmt.pix.field; + do_gettimeofday(&timevalue); + + v4l2_dbg(1, debug, vpfe_dev->driver, "\nStarting vpfe_isr..."); + + /* only for 6446 this will be applicable */ + if (!(ISNULL(ccdc_hw_dev.reset))) + ccdc_hw_dev.reset(); + + if (field == V4L2_FIELD_INTERLACED || + (field == V4L2_FIELD_SEQ_TB)) { + /* Interlaced */ + /* check which field we are in hardware */ + fid = ccdc_hw_dev.getfid(); + /* switch the software maintained field id */ + channel->field_id ^= 1; + v4l2_dbg(1, debug, vpfe_dev->driver, "field id = %x:%x.\n", fid, + channel->field_id); + if (fid == channel->field_id) { + /* we are in-sync here,continue */ + if (fid == 0) { + /* One frame is just being captured. If the + * next frame is available, release the current + * frame and move on + */ + if (common->curFrm != common->nextFrm) { + /* Copy frame capture time value in + * curFrm->ts + */ + common->curFrm->ts = timevalue; + common->curFrm->state = VIDEOBUF_DONE; + wake_up_interruptible(&common->curFrm-> + done); + common->curFrm = common->nextFrm; + } + /* based on whether the two fields are stored + * interleavely or separately in memory, + * reconfigure the CCDC memory address + */ + if (channel->out_from == VPFE_CCDC_OUT && + field == V4L2_FIELD_SEQ_TB) { + addr = + videobuf_to_dma_contig(common->curFrm); + addr += common->field_off; + ccdc_hw_dev.setfbaddr(addr); + } + } else if (fid == 1) { + /* if one field is just being captured + * configure the next frame + * get the next frame from the empty queue + * if no frame is available + * hold on to the current buffer + */ + if (channel->out_from == VPFE_CCDC_OUT && + !list_empty(&common->dma_queue) && + common->curFrm == common->nextFrm) { + common->nextFrm = + list_entry(common-> + dma_queue.next, + struct + videobuf_buffer, + queue); + list_del(&common->nextFrm->queue); + common->nextFrm->state = + VIDEOBUF_ACTIVE; + addr = videobuf_to_dma_contig(common-> + nextFrm); + ccdc_hw_dev.setfbaddr(addr); + } + } + } else if (fid == 0) { + /* recover from any hardware out-of-sync due to + * possible switch of video source + * for fid == 0, sync up the two fids + * for fid == 1, no action, one bad frame will + * go out, but it is not a big deal + */ + channel->field_id = fid; + } + } else if (field == V4L2_FIELD_NONE) { + + v4l2_dbg(1, debug, vpfe_dev->driver, + "\nframe format is progressive..."); + if (common->curFrm != common->nextFrm) { + /* Copy frame capture time value in curFrm->ts */ + common->curFrm->ts = timevalue; + common->curFrm->state = VIDEOBUF_DONE; + wake_up_interruptible(&common->curFrm->done); + common->curFrm = common->nextFrm; + } + + } + v4l2_dbg(1, debug, vpfe_dev->driver, "interrupt returned.\n"); + return IRQ_RETVAL(1); +} + +static irqreturn_t vdint1_isr(int irq, void *dev_id) +{ + struct channel_obj *channel = NULL; + struct common_obj *common = NULL; + struct vpfe_device *dev = dev_id; + unsigned long addr; + channel = dev->dev[VPFE_CHANNEL0_VIDEO]; + common = &(channel->common[VPFE_VIDEO_INDEX]); + + v4l2_dbg(1, debug, vpfe_dev->driver, "\nInside vdint1_isr..."); + + if ((common->fmt.fmt.pix.field == V4L2_FIELD_NONE) && + !list_empty(&common->dma_queue) && + common->curFrm == common->nextFrm) { + common->nextFrm = + list_entry(common->dma_queue.next, + struct videobuf_buffer, queue); + list_del(&common->nextFrm->queue); + common->nextFrm->state = VIDEOBUF_ACTIVE; + addr = videobuf_to_dma_contig(common->nextFrm); + ccdc_hw_dev.setfbaddr(addr); + } + return IRQ_RETVAL(1); +} + +static int vpfe_detach_irq(struct channel_obj *channel) +{ + enum ccdc_frmfmt frame_format; + int err = 0; + + /* First clear irq if already in use */ + switch (channel->irq_type) { + case VPFE_USE_CCDC_IRQ: + ccdc_hw_dev.get_frame_format(&frame_format); + if (frame_format == CCDC_FRMFMT_PROGRESSIVE) + free_irq(IRQ_VDINT1, &vpfe_obj); + channel->irq_type = VPFE_NO_IRQ; + break; + case VPFE_NO_IRQ: + break; + default: + return -1; + } + return err; +} + +static int vpfe_attach_irq(struct channel_obj *channel) +{ + enum ccdc_frmfmt frame_format; + int err = 0; + + channel->irq_type = VPFE_USE_CCDC_IRQ; + + switch (channel->irq_type) { + case VPFE_USE_CCDC_IRQ: + { + ccdc_hw_dev.get_frame_format(&frame_format); + if (frame_format == CCDC_FRMFMT_PROGRESSIVE) { + err = + request_irq(channel->ccdc_irq1, + vdint1_isr, + IRQF_DISABLED, + "vpfe_capture1", + (void *)&vpfe_obj); + if (err < 0) + return -1; + } + } + break; + default: + return -1; + } + return 0; +} + +/* vpfe_release : This function deletes buffer queue, frees the + * buffers and the vpfe file handle + */ +static int vpfe_release(struct file *filep) +{ + int ret; + struct common_obj *common = NULL; + /* Get the channel object and file handle object */ + struct vpfe_fh *fh = filep->private_data; + struct channel_obj *channel = fh->channel; + struct v4l2_int_device *dec = + channel->decoder[channel->current_decoder]; + + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); + common = &(channel->common[VPFE_VIDEO_INDEX]); + /* If this is doing IO and other channels are not closed */ + if ((channel->usrs != 1) && fh->io_allowed[VPFE_VIDEO_INDEX]) { + v4l2_err(vpfe_dev->driver, "Close other instances\n"); + return -EAGAIN; + } + /* Get the lock on channel object */ + ret = mutex_lock_interruptible(&common->lock); + if (ret) + return ret; + /* if this instance is doing IO */ + if (fh->io_allowed[VPFE_VIDEO_INDEX]) { + /* Reset io_usrs member of channel object */ + if (common->started) { + ccdc_hw_dev.enable(0); + if (ccdc_hw_dev.enable_out_to_sdram) + ccdc_hw_dev.enable_out_to_sdram(0); + if (vpfe_detach_irq(channel) < 0) { + v4l2_err(vpfe_dev->driver, + "Error in detaching IRQ\n"); + mutex_unlock(&common->lock); + return -EFAULT; + } + } + + common->io_usrs = 0; + /* Disable channel/vbi as per its device type and channel id */ + common->started = 0; + /* Free buffers allocated */ + common->numbuffers = + config_params.numbuffers[channel->channel_id]; + } + + /* Decrement channel usrs counter */ + channel->usrs--; + /* unlock semaphore on channel object */ + mutex_unlock(&common->lock); + /* Close the priority */ + v4l2_prio_close(&channel->prio, &fh->prio); + /* If this file handle has initialize decoder device, reset it */ + if (fh->initialized) { + vidioc_int_s_power(dec, 0); + channel->initialized = 0; + if (ccdc_hw_dev.close) + ccdc_hw_dev.close(vpfe_dev); + } + filep->private_data = NULL; + /* Free memory allocated to file handle object */ + kfree(fh); + + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); + return 0; +} + +/* vpfe_mmap : It is used to map kernel space buffers + * into user spaces + */ +static int vpfe_mmap(struct file *filep, struct vm_area_struct *vma) +{ + /* Get the channel object and file handle object */ + struct vpfe_fh *fh = filep->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common; + int err = 0; + v4l2_dbg(1, debug, vpfe_dev->driver, "Start of vpfe mmap\n"); + common = &(channel->common[VPFE_VIDEO_INDEX]); + + err = videobuf_mmap_mapper(&common->buffer_queue, vma); + v4l2_dbg(1, debug, vpfe_dev->driver, "End of vpfe mmap\n"); + return err; +} + +/* vpfe_poll: It is used for select/poll system call + */ +static unsigned int vpfe_poll(struct file *filep, poll_table *wait) +{ + int err = 0; + struct vpfe_fh *fh = filep->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = &(channel->common[VPFE_VIDEO_INDEX]); + + v4l2_dbg(1, debug, vpfe_dev->driver, ""); + + if (common->started) + err = videobuf_poll_stream(filep, &common->buffer_queue, wait); + + v4l2_dbg(1, debug, vpfe_dev->driver, ""); + return err; +} + +/* vpfe capture driver file operations */ +static struct v4l2_file_operations vpfe_fops = { + .owner = THIS_MODULE, + .open = vpfe_open, + .release = vpfe_release, + .ioctl = video_ioctl2, + .mmap = vpfe_mmap, + .poll = vpfe_poll +}; + +static struct vpfe_pixel_format * + vpfe_check_format(struct channel_obj *channel, + struct v4l2_pix_format *pixfmt, + int check) +{ + struct common_obj *common = &(channel->common[VPFE_VIDEO_INDEX]); + struct video_obj *vid_ch = &(channel->video); + struct vpfe_pixel_format *pix_fmt; + enum vpfe_hw_pix_format hw_pix; + int temp, found, hpitch, vpitch, bpp, min_height = 1, + min_width = 32, max_width, max_height; + + + temp = vpfe_lookup_hw_format(pixfmt->pixelformat); + if (temp < 0) { + if (check) { + v4l2_err(vpfe_dev->driver, "invalid pixel format\n"); + return NULL; + } + /* if invalid and this is a try format, then use hw default */ + pixfmt->pixelformat = common->fmt.fmt.pix.pixelformat; + /* Since this is hw default, we will find this pix format */ + temp = vpfe_lookup_hw_format(pixfmt->pixelformat); + + } else { + /* check if hw supports it */ + pix_fmt = &vpfe_pix_fmts[temp]; + temp = 0; + found = 0; + while (ccdc_hw_dev.enum_pix(&hw_pix, temp) >= 0) { + if (pix_fmt->hw_fmt == hw_pix) { + found = 1; + break; + } + temp++; + } + if (!found) { + if (check) { + v4l2_err(vpfe_dev->driver, "hw doesn't" + "support the pixel format\n"); + return NULL; + } + /* Since this is hw default, we will find this + * pix format + */ + pixfmt->pixelformat = common->fmt.fmt.pix.pixelformat; + temp = vpfe_lookup_hw_format(pixfmt->pixelformat); + } + } + pix_fmt = &vpfe_pix_fmts[temp]; + if (pixfmt->field == V4L2_FIELD_ANY) { + /* if ANY set the field to match with decoder */ + pixfmt->field = common->fmt.fmt.pix.field; + } + + /* Try matching the field with the decoder scan field */ + if (common->fmt.fmt.pix.field != pixfmt->field) { + if (!(VPFE_VALID_FIELD(pixfmt->field)) && check) { + v4l2_err(vpfe_dev->driver, "invalid field format\n"); + return NULL; + } + if (common->fmt.fmt.pix.field == V4L2_FIELD_INTERLACED) { + if (pixfmt->field != V4L2_FIELD_SEQ_TB) { + if (check) { + v4l2_err(vpfe_dev->driver, + "invalid field format\n"); + return NULL; + } + pixfmt->field = common->fmt.fmt.pix.field; + } + } else if (common->fmt.fmt.pix.field == V4L2_FIELD_NONE) { + if (check) { + v4l2_err(vpfe_dev->driver, + "invalid field format\n"); + return NULL; + } + pixfmt->field = common->fmt.fmt.pix.field; + } else + pixfmt->field = common->fmt.fmt.pix.field; + } + + if (pixfmt->field == V4L2_FIELD_INTERLACED) + min_height = 2; + + max_width = vid_ch->std_info.activepixels; + max_height = vid_ch->std_info.activelines; + if ((pixfmt->pixelformat == V4L2_PIX_FMT_SBGGR8) || + (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) || + (pixfmt->pixelformat == V4L2_PIX_FMT_SGRBG10DPCM8)) + bpp = 1; + else + bpp = 2; + min_width /= bpp; + hpitch = pixfmt->width; + vpitch = pixfmt->height; + v4l2_info(vpfe_dev->driver, "hpitch = %d, vpitch = %d, bpp = %d\n", + hpitch, vpitch, bpp); + if (hpitch < min_width) + hpitch = min_width; + if (vpitch < min_width) + vpitch = min_height; + + /* Check for upper limits of pitch */ + if (hpitch > max_width) + hpitch = max_width; + if (vpitch > max_height) + vpitch = max_height; + + /* recalculate bytesperline and sizeimage since width + * and height might have changed + */ + pixfmt->bytesperline = (((hpitch * bpp) + 31) & ~31); + if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) + pixfmt->sizeimage = pixfmt->bytesperline * vpitch + + ((pixfmt->bytesperline * vpitch) >> 1); + else + pixfmt->sizeimage = pixfmt->bytesperline * vpitch; + pixfmt->width = hpitch; + pixfmt->height = vpitch; + v4l2_info(vpfe_dev->driver, "adjusted hpitch = %d, vpitch =" + " %d, bpp = %d\n", hpitch, vpitch, bpp); + return pix_fmt; +} + +static int vpfe_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_QUERYCAP\n"); + memset(cap, 0, sizeof(*cap)); + if ((VPFE_CHANNEL0_VIDEO == channel->channel_id)) + *cap = vpfe_videocap; + else + return -EINVAL; + return 0; +} + +static int vpfe_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + int ret = 0; + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = NULL; + common = &(channel->common[VPFE_VIDEO_INDEX]); + + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_G_FMT\n"); + /* Fill in the information about + * format + */ + ret = mutex_lock_interruptible(&(common->lock)); + if (ret) + goto lock_out; + *fmt = common->fmt; +lock_out: + mutex_unlock(&(common->lock)); + return ret; +} + +static int vpfe_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + int ret; + enum vpfe_hw_pix_format hw_pix; + + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_ENUM_FMT\n"); + /* Fill in the information about format */ + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ret = ccdc_hw_dev.enum_pix(&hw_pix, fmt->index); + if (!ret) { + ret = vpfe_lookup_v4l2_pix_format(hw_pix); + if (ret >= 0) { + strcpy(fmt->description, vpfe_pix_fmts[ret].desc); + fmt->pixelformat = vpfe_pix_fmts[ret].pix_fmt; + ret = 0; + } + } + return ret; +} + +static int vpfe_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = NULL; + struct v4l2_rect win; + struct vpfe_pixel_format *pix_fmts; + int ret = 0; + common = &(channel->common[VPFE_VIDEO_INDEX]); + + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_S_FMT\n"); + /* If streaming is started, return error */ + if (common->started) { + v4l2_err(vpfe_dev->driver, "Streaming is started\n"); + ret = -EBUSY; + goto out; + } + /* Check for valid frame format */ + pix_fmts = vpfe_check_format(channel, &fmt->fmt.pix, 1); + + if (ISNULL(pix_fmts)) { + ret = -EINVAL; + goto out; + } + + /* store the pixel format in the channel + * object */ + ret = mutex_lock_interruptible(&common->lock); + if (ret) + goto out; + + /* First detach any IRQ if currently attached */ + if (vpfe_detach_irq(channel) < 0) { + v4l2_err(vpfe_dev->driver, "Error in detaching IRQ\n"); + ret = -EFAULT; + goto lock_out; + } + + common->fmt = *fmt; + + /* we are using same variable for setting crop window + * at ccdc. For ccdc, this is same as + * image window + */ + ccdc_hw_dev.get_image_window(&win); + win.width = common->fmt.fmt.pix.width; + win.height = common->fmt.fmt.pix.height; + ccdc_hw_dev.set_image_window(&win); + + /* In this case, image window and crop window are + * the same + */ + if (common->fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_SBGGR16) + ccdc_hw_dev.set_pixelformat(VPFE_BAYER); + else if (common->fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_SBGGR8) + ccdc_hw_dev.set_pixelformat(VPFE_BAYER_8BIT_PACK_ALAW); + else if (common->fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) + ccdc_hw_dev.set_pixelformat(VPFE_UYVY); + else if (common->fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) + ccdc_hw_dev.set_pixelformat(VPFE_YUYV); + else { + /* invalid pix format */ + ret = -EINVAL; + goto lock_out; + } + if (common->fmt.fmt.pix.field == + V4L2_FIELD_INTERLACED) { + ccdc_hw_dev.set_buftype(CCDC_BUFTYPE_FLD_INTERLEAVED); + ccdc_hw_dev.set_frame_format(CCDC_FRMFMT_INTERLACED); + } else if (common->fmt.fmt.pix.field == + V4L2_FIELD_SEQ_TB) { + ccdc_hw_dev.set_buftype(CCDC_BUFTYPE_FLD_SEPARATED); + ccdc_hw_dev.set_frame_format(CCDC_FRMFMT_INTERLACED); + } else if (common->fmt.fmt.pix.field == V4L2_FIELD_NONE) + ccdc_hw_dev.set_frame_format(CCDC_FRMFMT_PROGRESSIVE); + else { + v4l2_err(vpfe_dev->driver, "\n field error!"); + ret = -EINVAL; + } +lock_out: + mutex_unlock(&common->lock); +out: + return ret; +} + +static int vpfe_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct vpfe_pixel_format *pix_fmts; + + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_TRY_FMT\n"); + + pix_fmts = vpfe_check_format(channel, &f->fmt.pix, 0); + if (ISNULL(pix_fmts)) + return -EINVAL; + return 0; +} + +static void vpfe_config_format(struct channel_obj *ch) +{ + struct common_obj *common = &(ch->common[VPFE_VIDEO_INDEX]); + struct v4l2_rect win; + struct video_obj *vid_ch = NULL; + + vid_ch = &(ch->video); + common->crop.top = 0; + common->crop.top = 0; + common->crop.width = common->fmt.fmt.pix.width = + vid_ch->std_info.activepixels; + common->crop.height = common->fmt.fmt.pix.height = + vid_ch->std_info.activelines; + win.top = common->crop.top; + win.left = common->crop.left; + win.width = common->fmt.fmt.pix.width; + win.height = common->fmt.fmt.pix.height; + ccdc_hw_dev.set_image_window(&win); + if (vid_ch->std_info.frame_format) { + common->fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; + ccdc_hw_dev.set_frame_format(CCDC_FRMFMT_INTERLACED); + ccdc_hw_dev.set_buftype(CCDC_BUFTYPE_FLD_INTERLEAVED); + } else { + common->fmt.fmt.pix.field = V4L2_FIELD_NONE; + ccdc_hw_dev.set_frame_format(CCDC_FRMFMT_PROGRESSIVE); + } + ccdc_hw_dev.get_line_length(&common->fmt.fmt.pix.bytesperline); + common->fmt.fmt.pix.sizeimage = common->fmt.fmt.pix.bytesperline * + common->fmt.fmt.pix.height; +} + +static int vpfe_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct vpfe_capture_input *vpfe_inputs = channel->video.input; + int ret = -EINVAL; + + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_ENUMINPUT\n"); + + if (inp->index > vpfe_inputs->num_inputs) + return ret; + + if (vpfe_inputs->inputs[inp->index].input.name[0]) { + memcpy(inp, &vpfe_inputs->inputs[inp->index].input, + sizeof(struct v4l2_input)); + return 0; + } + return ret; +} + +static int vpfe_g_input(struct file *file, void *priv, unsigned int *index) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = + &(channel->common[VPFE_VIDEO_INDEX]); + struct vpfe_capture_input *vpfe_inputs = channel->video.input; + int ret = 0; + + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_G_INPUT\n"); + ret = mutex_lock_interruptible(&common->lock); + if (!ret) + *index = vpfe_inputs->current_input; + mutex_unlock(&common->lock); + return ret; +} + + +static int vpfe_s_input(struct file *file, void *priv, unsigned int index) +{ + int i, ret = -EINVAL; + v4l2_std_id std_id; + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct v4l2_int_device *new_dec, *curr_dec = + channel->decoder[channel->current_decoder]; + struct common_obj *common = + &(channel->common[VPFE_VIDEO_INDEX]); + struct vpfe_capture_input *vpfe_inputs = channel->video.input; + char *new_dec_name; + + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_S_INPUT\n"); + if (index > vpfe_inputs->num_inputs) { + v4l2_err(vpfe_dev->driver, "input index exceeds limit\n"); + return ret; + } + + if (!vpfe_inputs->inputs[index].input.name[0]) { + v4l2_err(vpfe_dev->driver, "input index exceeds limit\n"); + return ret; + } + + ret = mutex_lock_interruptible(&common->lock); + if (ret) + goto out; + /* If streaming is started return device busy + * error + */ + if (common->started) { + v4l2_err(vpfe_dev->driver, "Streaming is on\n"); + ret = -EBUSY; + goto lock_out; + } + new_dec_name = vpfe_inputs->inputs[index].dec_name; + /* switch in new decoder to be active */ + if (strcmp(new_dec_name, curr_dec->name)) { + for (i = 0; i < VPFE_CAPTURE_NUM_DECODERS; i++) { + if (channel->decoder[i] && + !strcmp(new_dec_name, + channel->decoder[i]->name)) { + new_dec = channel->decoder[i]; + channel->current_decoder = i; + /* Deinitialize the previous decoder + * and power down + */ + vidioc_int_s_power(curr_dec, 0); + + ret = vidioc_int_s_power(new_dec, 1); + if (ret) + goto lock_out; + ret = vidioc_int_init(new_dec); + if (ret) + goto lock_out; + curr_dec = new_dec; + } + } + + if (i == VPFE_CAPTURE_NUM_DECODERS) + /* couldn't find the decoder */ + goto lock_out; + } + ret = 0; + /* Set the input in the decoder */ + if (vpfe_inputs->inputs[index].routing_supported) + ret = vidioc_int_s_video_routing(curr_dec, + &vpfe_inputs->inputs[index].route); + + if (ret) { + v4l2_err(vpfe_dev->driver, + "vpfe_doioctl:error in setting input in decoder \n"); + ret = -EINVAL; + goto lock_out; + } + + vpfe_inputs->current_input = index; + ret = vpfe_set_hw_if_type(channel); + if (ret) + goto lock_out; + + ret = vpfe_config_default_format(channel); + if (ret) + goto lock_out; + + /* Detect default standard */ + ret = vidioc_int_querystd(curr_dec, &std_id); + if (!ret) + ret = vpfe_get_stdinfo(channel, &std_id); + + if (ret) + goto lock_out; + + vpfe_config_format(channel); +lock_out: + mutex_unlock(&common->lock); +out: + return ret; +} + +static int vpfe_querystd(struct file *file, void *priv, v4l2_std_id *std_id) +{ + int ret = 0; + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = + &(channel->common[VPFE_VIDEO_INDEX]); + struct v4l2_int_device *dec = + channel->decoder[channel->current_decoder]; + ret = mutex_lock_interruptible(&common->lock); + if (ret) + goto lock_out; + /* Call querystd function of decoder device */ + ret = vidioc_int_querystd(dec, std_id); + /* Set format based on the standard selected */ + if (!ret) + ret = vpfe_get_stdinfo(channel, std_id); + vpfe_config_format(channel); +lock_out: + mutex_unlock(&common->lock); + return ret; +} + +static int vpfe_s_std(struct file *file, void *priv, v4l2_std_id *std_id) +{ + int ret = 0; + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = + &(channel->common[VPFE_VIDEO_INDEX]); + struct v4l2_int_device *dec = + channel->decoder[channel->current_decoder]; + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_S_STD\n"); + + /* If streaming is started, return device + busy error */ + if (common->started) { + v4l2_err(vpfe_dev->driver, "streaming is started\n"); + ret = -EBUSY; + goto out; + } + /* Call decoder driver function to set the + standard */ + ret = mutex_lock_interruptible(&common->lock); + if (ret) + goto out; + ret = vidioc_int_s_std(dec, std_id); + + /* If it returns error, return error */ + if (!ret) + ret = vpfe_get_stdinfo(channel, std_id); + + if (!ret) + vpfe_config_format(channel); +out: + mutex_unlock(&common->lock); + return ret; +} + +static int vpfe_g_std(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct v4l2_int_device *dec = + channel->decoder[channel->current_decoder]; + int ret; + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_G_STD\n"); + ret = vidioc_int_querystd(dec, std_id); + if (ret) + goto out; + + ret = vpfe_get_stdinfo(channel, std_id); + if (!ret) + vpfe_config_format(channel); +out: + return ret; +} +/* + * Videobuf operations + */ +static int vpfe_videobuf_setup(struct videobuf_queue *vq, + unsigned int *count, + unsigned int *size) +{ + /* Get the file handle object and channel object */ + struct vpfe_fh *fh = vq->priv_data; + struct channel_obj *channel = fh->channel; + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); + *size = config_params.channel_bufsize[channel->channel_id]; + + if (*count < config_params.min_numbuffers) + *count = config_params.min_numbuffers; + v4l2_dbg(1, debug, vpfe_dev->driver, + "count=%d, size=%d\n", *count, *size); + return 0; +} + +static int vpfe_videobuf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + int ret = 0; + /* Get the file handle object and channel object */ + struct vpfe_fh *fh = vq->priv_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common; + unsigned long addr; + + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); + + common = &(channel->common[VPFE_VIDEO_INDEX]); + + if (V4L2_MEMORY_USERPTR == common->memory) { + /* we don't support user ptr IO */ + v4l2_dbg(1, debug, vpfe_dev->driver, + "\n"); + ret = -EINVAL; + goto out; + } + + /* If buffer is not initialized, initialize it */ + if (VIDEOBUF_NEEDS_INIT == vb->state) { + vb->width = common->width; + vb->height = common->height; + vb->size = vb->width * vb->height * 2; + vb->field = field; + } + addr = videobuf_to_dma_contig(vb); + if (vq->streaming) { + if (!ISALIGNED(addr)) { + v4l2_err(vpfe_dev->driver, "buffer_prepare:offset is" + "not aligned to 32 bytes\n"); + ret = -EINVAL; + goto out; + } + } + vb->state = VIDEOBUF_PREPARED; + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); +out: + return ret; +} + +static void vpfe_videobuf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + /* Get the file handle object and channel object */ + struct vpfe_fh *fh = vq->priv_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = NULL; + + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); + common = &(channel->common[VPFE_VIDEO_INDEX]); + + /* add the buffer to the DMA queue */ + list_add_tail(&vb->queue, &common->dma_queue); + /* Change state of the buffer */ + vb->state = VIDEOBUF_QUEUED; + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); +} + +static void vpfe_videobuf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + v4l2_dbg(1, debug, vpfe_dev->driver, "vpfe_videobuf_release\n"); + videobuf_dma_contig_free(vq, vb); + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static struct videobuf_queue_ops vpfe_videobuf_qops = { + .buf_setup = vpfe_videobuf_setup, + .buf_prepare = vpfe_videobuf_prepare, + .buf_queue = vpfe_videobuf_queue, + .buf_release = vpfe_videobuf_release, +}; + +static int vpfe_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + int ret = 0; + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = NULL; + enum v4l2_field field; + + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); + common = &(channel->common[VPFE_VIDEO_INDEX]); + + v4l2_dbg(1, debug, vpfe_dev->driver, "\nEnd of VIDIOC_REQBUFS ioctl"); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != p->type) { + ret = -EINVAL; + goto out; + } + if (common->io_usrs != 0) { + ret = -EBUSY; + goto out; + } + + ret = mutex_lock_interruptible(&common->lock); + if (ret) + goto out; + + if (common->fmt.fmt.pix.field != V4L2_FIELD_ANY) + field = common->fmt.fmt.pix.field; + else if (channel->vpfe_if == VPFE_RAW_BAYER) + field = V4L2_FIELD_NONE; + else + field = V4L2_FIELD_INTERLACED; + + videobuf_queue_dma_contig_init(&common->buffer_queue, + &vpfe_videobuf_qops, + NULL, + &common->irqlock, + p->type, + field, + sizeof(struct videobuf_buffer), + fh); + + fh->io_allowed[VPFE_VIDEO_INDEX] = 1; + common->io_usrs = 1; + INIT_LIST_HEAD(&common->dma_queue); + ret = videobuf_reqbufs(&common->buffer_queue, p); + mutex_unlock(&common->lock); +out: + return ret; +} + +static int vpfe_querybuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = NULL; + int ret = 0; + u8 buf_type_index = 0; + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_QUERYBUF\n"); + buf_type_index = VPFE_VIDEO_INDEX; + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != p->type) { + v4l2_err(vpfe_dev->driver, + "VIDIOC_QUERYBUF:Invalid buf type\n"); + ret = -EINVAL; + goto out; + } + common = &(channel->common[buf_type_index]); + if (p->memory != V4L2_MEMORY_MMAP) { + v4l2_err(vpfe_dev->driver, + "VIDIOC_QUERYBUF:Invalid memory\n"); + ret = -EINVAL; + goto out; + } + /* Call videobuf_querybuf to get information */ + return videobuf_querybuf(&common->buffer_queue, p); +out: + return ret; +} + +static int vpfe_qbuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = NULL; + int buf_type_index, ret = 0; + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_QBUF\n"); + buf_type_index = VPFE_VIDEO_INDEX; + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != p->type) { + v4l2_err(vpfe_dev->driver, "VIDIOC_QBUF:Invalid buf type\n"); + ret = -EINVAL; + goto out; + } + common = &(channel->common[buf_type_index]); + + /* If this file handle is not allowed to do IO, + * return error + */ + if (!fh->io_allowed[buf_type_index]) { + v4l2_err(vpfe_dev->driver, "fh->io_allowed\n"); + ret = -EACCES; + goto out; + } + return videobuf_qbuf(&common->buffer_queue, p); +out: + return ret; +} +static int vpfe_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = NULL; + int buf_type_index = 0, ret = 0; + buf_type_index = VPFE_VIDEO_INDEX; + common = &(channel->common[buf_type_index]); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != p->type) { + v4l2_err(vpfe_dev->driver, "VIDIOC_DQBUF:Invalid buf type\n"); + ret = -EINVAL; + goto out; + } + if (file->f_flags & O_NONBLOCK) + ret = videobuf_dqbuf(&common->buffer_queue, p, 1); + else + ret = videobuf_dqbuf(&common->buffer_queue, p, 0); +out: + return ret; +} + +/* vpfe_calculate_offsets : This function calculates buffers offset + * for top and bottom field + */ +static void vpfe_calculate_offsets(struct channel_obj *channel) +{ + struct common_obj *common = &(channel->common[VPFE_VIDEO_INDEX]); + + struct v4l2_rect image_win; + + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); + + common->field_off = 0; + ccdc_hw_dev.get_image_window(&image_win); + common->field_off = (image_win.height - 2) * image_win.width; + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); +} + +static int vpfe_streamon(struct file *file, void *priv, + enum v4l2_buf_type i) +{ + int ret = 0; + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = NULL; + int buf_type_index = VPFE_VIDEO_INDEX; + unsigned long addr; + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_STREAMON\n"); + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != i) { + v4l2_err(vpfe_dev->driver, + "VIDIOC_STREAMON:Invalid buf type\n"); + ret = -EINVAL; + goto out; + } + common = &(channel->common[buf_type_index]); + /* If file handle is not allowed IO, + * return error + */ + if (!fh->io_allowed[buf_type_index]) { + v4l2_err(vpfe_dev->driver, "fh->io_allowed\n"); + ret = -EACCES; + goto out; + } + /* If Streaming is already started, + * return error + */ + if (common->started) { + v4l2_err(vpfe_dev->driver, "channel->started\n"); + ret = -EBUSY; + goto out; + } + /* Call videobuf_streamon to start streaming + * in videobuf + */ + ret = videobuf_streamon(&common->buffer_queue); + if (ret) + goto out; + + ret = mutex_lock_interruptible(&common->lock); + if (ret) + goto out; + /* If buffer queue is empty, return error */ + if (list_empty(&common->dma_queue)) { + v4l2_err(vpfe_dev->driver, "buffer queue is empty\n"); + ret = -EIO; + goto lock_out; + } + /* Get the next frame from the buffer queue */ + common->nextFrm = common->curFrm = + list_entry(common->dma_queue.next, + struct videobuf_buffer, queue); + /* Remove buffer from the buffer queue */ + list_del(&common->curFrm->queue); + /* Mark state of the current frame to active */ + common->curFrm->state = VIDEOBUF_ACTIVE; + /* Initialize field_id and started member */ + channel->field_id = 0; + common->started = 1; + + addr = videobuf_to_dma_contig(common->curFrm); + + /* Calculate field offset */ + vpfe_calculate_offsets(channel); + + if (vpfe_attach_irq(channel) < 0) { + v4l2_err(vpfe_dev->driver, + "Error in attaching interrupt handle\n"); + ret = -EFAULT; + goto lock_out; + } + + ccdc_hw_dev.configure(); + ccdc_hw_dev.setfbaddr((unsigned long)(addr)); + ccdc_hw_dev.enable(1); + if (ccdc_hw_dev.enable_out_to_sdram) + ccdc_hw_dev.enable_out_to_sdram(1); +lock_out: + mutex_unlock(&common->lock); +out: + return ret; +} + +static int vpfe_streamoff(struct file *file, void *priv, + enum v4l2_buf_type i) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = NULL; + int buf_type_index = VPFE_VIDEO_INDEX, ret = 0; + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != i) { + v4l2_err(vpfe_dev->driver, + "VIDIOC_STREAMOFF:Invalid buf type\n"); + return -EINVAL; + } + common = &(channel->common[buf_type_index]); + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_STREAMOFF\n"); + /* If io is allowed for this file handle, + * return error + */ + if (!fh->io_allowed[buf_type_index]) { + v4l2_err(vpfe_dev->driver, "fh->io_allowed\n"); + ret = -EACCES; + goto out; + } + /* If streaming is not started, return error */ + if (!common->started) { + v4l2_err(vpfe_dev->driver, "channel->started\n"); + ret = -EINVAL; + goto out; + } + ret = mutex_lock_interruptible(&common->lock); + if (ret) + goto out; + common->started = 0; + ccdc_hw_dev.enable(0); + if (ccdc_hw_dev.enable_out_to_sdram) + ccdc_hw_dev.enable_out_to_sdram(0); + if (vpfe_detach_irq(channel) < 0) { + v4l2_err(vpfe_dev->driver, + "Error in detaching interrupt handler\n"); + mutex_unlock(&common->lock); + ret = -EFAULT; + goto lock_out; + } + ret = videobuf_streamoff(&common->buffer_queue); +lock_out: + mutex_unlock(&common->lock); +out: + return ret; +} + +static int vpfe_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct v4l2_int_device *dec = + channel->decoder[channel->current_decoder]; + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_QUERYCTRL\n"); + /* Call queryctrl function of decoder device */ + return vidioc_int_queryctrl(dec, qc); +} + +static int vpfe_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct v4l2_int_device *dec = + channel->decoder[channel->current_decoder]; + struct common_obj *common = + &(channel->common[VPFE_VIDEO_INDEX]); + int ret = 0; + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_G_CTRL\n"); + /* Call getcontrol function of decoder device */ + ret = mutex_lock_interruptible(&common->lock); + if (ret) + return ret; + ret = vidioc_int_g_ctrl(dec, ctrl); + mutex_unlock(&common->lock); + return ret; +} + +static int vpfe_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + int ret = 0; + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = + &(channel->common[VPFE_VIDEO_INDEX]); + struct v4l2_int_device *dec = + channel->decoder[channel->current_decoder]; + v4l2_dbg(1, debug, vpfe_dev->driver, "VIDIOC_S_CTRL\n"); + /* Call setcontrol function of decoder device */ + ret = mutex_lock_interruptible(&common->lock); + if (ret) + return ret; + ret = vidioc_int_s_ctrl(dec, ctrl); + mutex_unlock(&common->lock); + return ret; +} + +static int vpfe_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *crop) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct video_obj *vid_ch = NULL; + vid_ch = &(channel->video); + + if (vid_ch->index > vpfe_max_standards) + return -EINVAL; + memset(crop, 0, sizeof(struct v4l2_cropcap)); + crop->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop->bounds.width = crop->defrect.width = + vpfe_standards[vid_ch->index].width; + crop->bounds.height = crop->defrect.height = + vpfe_standards[vid_ch->index].height; + crop->pixelaspect = vpfe_standards[vid_ch->index].pixelaspect; + return 0; +} + +static int vpfe_g_crop(struct file *file, void *priv, + struct v4l2_crop *crop) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = NULL; + common = &(channel->common[VPFE_VIDEO_INDEX]); + v4l2_dbg(1, debug, vpfe_dev->driver, "\nStarting VIDIOC_G_CROP ioctl"); + crop->c = common->crop; + return 0; +} + +static int vpfe_s_crop(struct file *file, void *priv, + struct v4l2_crop *crop) +{ + int ret = 0; + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = NULL; + struct video_obj *vid_ch = NULL; + common = &(channel->common[VPFE_VIDEO_INDEX]); + vid_ch = &(channel->video); + v4l2_dbg(1, debug, vpfe_dev->driver, "\nStarting VIDIOC_S_CROP ioctl"); + if (common->started) { + /* make sure streaming is not started */ + v4l2_err(vpfe_dev->driver, + "Cannot change crop when streaming is ON\n"); + ret = -EBUSY; + goto out; + } + + ret = mutex_lock_interruptible(&common->lock); + if (ret) + goto out; + /* make sure parameters are valid */ + if ((crop->c.left + crop->c.width <= vid_ch->std_info.activepixels) && + (crop->c.top + crop->c.height <= vid_ch->std_info.activelines)) { + /* adjust the width to 16 pixel boundry */ + crop->c.width = ((crop->c.width + 15) & ~0xf); + ccdc_hw_dev.set_image_window(&crop->c); + common->fmt.fmt.pix.width = crop->c.width; + common->fmt.fmt.pix.height = crop->c.height; + ccdc_hw_dev.get_line_length(&common->fmt.fmt.pix.bytesperline); + common->fmt.fmt.pix.sizeimage = + common->fmt.fmt.pix. + bytesperline * + common->fmt.fmt.pix.height; + common->crop = crop->c; + } else { + v4l2_err(vpfe_dev->driver, "Error in S_CROP params\n"); + ret = -EINVAL; + } + mutex_unlock(&common->lock); +out: + return ret; +} + + +static long vpfe_param_handler(struct file *file, void *priv, + int cmd, void *param) +{ + struct vpfe_fh *fh = file->private_data; + struct channel_obj *channel = fh->channel; + struct common_obj *common = NULL; + int ret = 0; + common = &(channel->common[VPFE_VIDEO_INDEX]); + + if (common->started) { + /* only allowed if streaming is not started */ + v4l2_err(vpfe_dev->driver, "channel already started\n"); + ret = -EBUSY; + goto out; + } + ret = mutex_lock_interruptible(&common->lock); + if (ret) + goto out; + switch (cmd) { + case VPFE_CMD_S_SOC_PARAMS: + { + ret = ccdc_hw_dev.setparams(param); + if (ret) { + v4l2_err(vpfe_dev->driver, + "Error in setting parameters" + " in CCDC \n"); + goto lock_out; + } + if (vpfe_get_image_format(&common->fmt) < 0) { + v4l2_err(vpfe_dev->driver, + "Invalid image format at CCDC \n"); + goto lock_out; + } + break; + } + default: + ret = -EINVAL; + } +lock_out: + mutex_unlock(&common->lock); +out: + return ret; +} + + +/* vpfe capture ioctl operations */ +static const struct v4l2_ioctl_ops vpfe_ioctl_ops = { + .vidioc_querycap = vpfe_querycap, + .vidioc_g_fmt_vid_cap = vpfe_g_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = vpfe_enum_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vpfe_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vpfe_try_fmt_vid_cap, + .vidioc_enum_input = vpfe_enum_input, + .vidioc_g_input = vpfe_g_input, + .vidioc_s_input = vpfe_s_input, + .vidioc_querystd = vpfe_querystd, + .vidioc_s_std = vpfe_s_std, + .vidioc_g_std = vpfe_g_std, + .vidioc_reqbufs = vpfe_reqbufs, + .vidioc_querybuf = vpfe_querybuf, + .vidioc_qbuf = vpfe_qbuf, + .vidioc_dqbuf = vpfe_dqbuf, + .vidioc_streamon = vpfe_streamon, + .vidioc_streamoff = vpfe_streamoff, + .vidioc_queryctrl = vpfe_queryctrl, + .vidioc_g_ctrl = vpfe_g_ctrl, + .vidioc_s_ctrl = vpfe_s_ctrl, + .vidioc_cropcap = vpfe_cropcap, + .vidioc_g_crop = vpfe_g_crop, + .vidioc_s_crop = vpfe_s_crop, + .vidioc_default = vpfe_param_handler, +}; + +/* vpfe_probe : This function creates device entries by register + * itself to the V4L2 driver and initializes fields of each + * channel objects + */ +static __init int vpfe_probe(struct platform_device *device) +{ + struct common_obj *common = NULL; + int err = -ENOMEM, index = 0; + struct video_device *vfd = NULL; + struct channel_obj *channel = NULL; + struct video_obj *vid_ch = NULL; + struct resource *res1, *res2; + void *__iomem mem1; + void *__iomem mem2; + + vpfe_dev = &device->dev; + + /* Get the pointer to the channel object */ + channel = vpfe_obj.dev[0]; + /* Allocate memory for video device */ + vfd = video_device_alloc(); + if (ISNULL(vfd)) { + v4l2_err(vpfe_dev->driver, + "Unable to alloc video device\n"); + return err; + } + + /* Initialize field of video device */ + vfd->release = video_device_release; + vfd->current_norm = V4L2_STD_UNKNOWN; + vfd->fops = &vpfe_fops; + vfd->ioctl_ops = &vpfe_ioctl_ops; + vfd->minor = -1; + vfd->tvnorms = V4L2_STD_UNKNOWN, + vfd->dev = device->dev; + snprintf(vfd->name, sizeof(vfd->name), + "%s_V%d.%d.%d", + CAPTURE_DRV_NAME, + (VPFE_CAPTURE_VERSION_CODE >> 16) & 0xff, + (VPFE_CAPTURE_VERSION_CODE >> 8) & 0xff, + (VPFE_CAPTURE_VERSION_CODE) & 0xff); + /* Set video_dev to the video device */ + channel->video_dev = vfd; + + channel->usrs = 0; + common = &(channel->common[VPFE_VIDEO_INDEX]); + common->io_usrs = 0; + common->started = 0; + spin_lock_init(&common->irqlock); + common->numbuffers = 0; + common->field_off = 0; + common->curFrm = common->nextFrm = NULL; + memset(&common->fmt, 0, sizeof(struct v4l2_format)); + channel->initialized = 0; + channel->channel_id = 0; + vid_ch = &(channel->video); + vid_ch->input = device->dev.platform_data; + if (!vid_ch->input) { + v4l2_err(vpfe_dev->driver, + "Unable to get inputs to vpfe\n"); + err = -ENOENT; + goto probe_out_release; + } + vid_ch->index = 0; + channel->irq_type = VPFE_NO_IRQ; + /* Get VINT0 irq resource */ + res1 = platform_get_resource(device, IORESOURCE_IRQ, 0); + if (!res1) { + err = -ENOENT; + v4l2_err(vpfe_dev->driver, "Unable to get interrupt for VINT0"); + goto probe_out_release; + } + channel->ccdc_irq0 = res1->start; + + /* Get VINT1 irq resource */ + res1 = platform_get_resource(device, + IORESOURCE_IRQ, 1); + if (!res1) { + err = -ENOENT; + v4l2_err(vpfe_dev->driver, + "Unable to get interrupt for VINT1"); + goto probe_out_release; + } + channel->ccdc_irq1 = res1->start; + channel->res1 = platform_get_resource(device, IORESOURCE_MEM, 0); + channel->res2 = platform_get_resource(device, IORESOURCE_MEM, 1); + if (!channel->res1 || !channel->res2) { + v4l2_err(vpfe_dev->driver, + "Unable to get register address map\n"); + err = -ENOENT; + goto probe_out_release; + } + res1 = (struct resource *)channel->res1; + res2 = (struct resource *)channel->res2; + if (!request_mem_region(res1->start, res1->end - res1->start + 1, + vpfe_dev->driver->name)) { + err = -ENXIO; + v4l2_err(vpfe_dev->driver, + "Failed request_mem_region for ccdc base\n"); + goto probe_out_release; + } + + mem1 = ioremap_nocache(res1->start, res1->end - res1->start + 1); + if (!mem1) { + v4l2_err(vpfe_dev->driver, "Unable to ioremap ccdc address\n"); + goto probe_out_release_mem1; + } + + ccdc_hw_dev.set_ccdc_base(mem1, res1->end - res1->start + 1); + + if (!request_mem_region(res2->start, res2->end - res2->start + 1, + vpfe_dev->driver->name)) { + err = -ENXIO; + v4l2_err(vpfe_dev->driver, + "Failed request_mem_region for" + " vpss base\n"); + goto probe_out_unmap1; + } + + mem2 = ioremap_nocache(res2->start, res2->end - res2->start + 1); + if (!mem2) { + v4l2_err(vpfe_dev->driver, "Unable to ioremap vpss address\n"); + goto probe_out_release_mem2; + } + + ccdc_hw_dev.set_vpss_base(mem2, res2->end - res2->start + 1); + + err = request_irq(channel->ccdc_irq0, vpfe_isr, IRQF_DISABLED, + "vpfe_capture0", (void *)&vpfe_obj); + + if (0 != err) { + v4l2_err(vpfe_dev->driver, + "Unable to request interrupt\n"); + goto probe_out_unmap2; + } + + /* Initialize field of the channel objects */ + channel->usrs = common->io_usrs = 0; + common->started = channel->initialized = 0; + channel->channel_id = 0; + common->numbuffers = config_params.numbuffers[channel->channel_id]; + channel->numdecoders = 0; + channel->current_decoder = 0; + for (index = 0; index < VPFE_CAPTURE_NUM_DECODERS; index++) + channel->decoder[index] = NULL; + + /* Initialize prio member of channel object */ + v4l2_prio_init(&channel->prio); + + /* register video device */ + v4l2_dbg(1, debug, vpfe_dev->driver, + "trying to register vpfe device.\n"); + v4l2_dbg(1, debug, vpfe_dev->driver, + "channel=%x,channel->video_dev=%x\n", + (int)channel, (int)&channel->video_dev); + channel->common[VPFE_VIDEO_INDEX].fmt.type = + V4L2_BUF_TYPE_VIDEO_CAPTURE; + err = video_register_device(channel->video_dev, + VFL_TYPE_GRABBER, vpfe_nr[0]); + + dev_notice(vpfe_dev, "video device registered\n"); + if (err) { + v4l2_err(vpfe_dev->driver, + "Unable to register video device.\n"); + goto probe_out_release_irq; + } + + vpfe_capture.priv = channel; + err = v4l2_int_device_register(&vpfe_capture); + if (err) { + v4l2_err(vpfe_dev->driver, + "Unable to register int master device.\n"); + goto probe_out; + } + dev_notice(vpfe_dev, "v4l2 int master registered\n"); + mutex_init(&common->lock); + return 0; + +probe_out: + /* Get the pointer to the channel object */ + channel = vpfe_obj.dev[0]; + /* Unregister video device */ + video_unregister_device(channel->video_dev); + v4l2_int_device_unregister(&vpfe_capture); + +probe_out_release_irq: + free_irq(channel->ccdc_irq0, (void *)&vpfe_obj); +probe_out_unmap2: + iounmap(mem2); +probe_out_unmap1: + iounmap(mem1); +probe_out_release_mem1: + release_mem_region(res1->start, res1->end - + res1->start + 1); +probe_out_release_mem2: + release_mem_region(res2->start, + res2->end - + res2->start + 1); +probe_out_release: + video_device_release(channel->video_dev); + channel->video_dev = NULL; + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); + return err; +} + +/* vpfe_remove : It un-register channels from V4L2 driver + */ +static int vpfe_remove(struct platform_device *device) +{ + struct channel_obj *channel; + struct common_obj *common = NULL; + struct resource *res; + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); + + /* un-register device */ + channel = vpfe_obj.dev[0]; + common = &(channel->common[VPFE_VIDEO_INDEX]); + free_irq(channel->ccdc_irq0, (void *)&vpfe_obj); + /* Unregister video device */ + video_unregister_device(channel->video_dev); + video_device_release(channel->video_dev); + v4l2_int_device_unregister(&vpfe_capture); + channel->video_dev = NULL; + res = (struct resource *)channel->res1; + release_mem_region(res->start, res->end - res->start + 1); + res = (struct resource *)channel->res2; + release_mem_region(res->start, res->end - res->start + 1); + iounmap(ccdc_hw_dev.get_ccdc_base()); + iounmap(ccdc_hw_dev.get_vpss_base()); + v4l2_dbg(1, debug, vpfe_dev->driver, "\n"); + return 0; +} + +static int +vpfe_suspend(struct platform_device *dev, pm_message_t state) +{ + /* add suspend code here later */ + return 0; +} + +static int +vpfe_resume(struct platform_device *dev) +{ + /* add resume code here later */ + return 0; +} + +static struct platform_driver vpfe_driver = { + .driver = { + .name = CAPTURE_DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = vpfe_probe, + .remove = __devexit_p(vpfe_remove), + .suspend = vpfe_suspend, + .resume = vpfe_resume, +}; + +static __init int vpfe_init(void) +{ + int err = 0; + + /* Default number of bu