diff options
Diffstat (limited to 'drivers/media/video')
53 files changed, 31182 insertions, 2 deletions
diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index dcf9fa9264bb..c64c8d201262 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -550,6 +550,57 @@ config VIDEO_W9966 Check out <file:Documentation/video4linux/w9966.txt> for more information. +config VIDEO_MXC_CAMERA + tristate "MXC Video For Linux Camera" + depends on VIDEO_DEV && ARCH_MXC + default y + ---help--- + This is the video4linux2 capture driver based on MXC IPU/eMMA module. + +source "drivers/media/video/mxc/capture/Kconfig" + +config VIDEO_MXC_OUTPUT + tristate "MXC Video For Linux Video Output" + depends on VIDEO_DEV && ARCH_MXC + default y + ---help--- + This is the video4linux2 output driver based on MXC IPU/eMMA module. + +source "drivers/media/video/mxc/output/Kconfig" + +config VIDEO_PXP + tristate "STMP3XXX PxP" + depends on VIDEO_DEV && VIDEO_V4L2 && ARCH_STMP3XXX + select VIDEOBUF_DMA_CONTIG + ---help--- + This is a video4linux driver for the Freescale PxP + (Pixel Pipeline). This module supports output overlay of + the STMP3xxx framebuffer on a video stream. + + To compile this driver as a module, choose M here: the + module will be called pxp. + +config VIDEO_MXS_PXP + tristate "MXS PxP" + depends on VIDEO_DEV && VIDEO_V4L2 && ARCH_MXS + select VIDEOBUF_DMA_CONTIG + ---help--- + This is a video4linux driver for the Freescale PxP + (Pixel Pipeline). This module supports output overlay of + the MXS framebuffer on a video stream. + + To compile this driver as a module, choose M here: the + module will be called pxp. + +config VIDEO_MXC_OPL + tristate + depends on VIDEO_DEV && ARCH_MXC + default n + ---help--- + This is the ARM9-optimized OPL (Open Primitives Library) software + rotation/mirroring implementation. It may be used by eMMA video + capture or output device. + config VIDEO_CPIA tristate "CPiA Video For Linux" depends on VIDEO_V4L1 diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 9f2e3214a482..49cb18802024 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -88,6 +88,15 @@ obj-$(CONFIG_VIDEO_W9966) += w9966.o obj-$(CONFIG_VIDEO_PMS) += pms.o obj-$(CONFIG_VIDEO_VINO) += vino.o obj-$(CONFIG_VIDEO_STRADIS) += stradis.o +obj-$(CONFIG_VIDEO_MXC_IPU_CAMERA) += mxc/capture/ +obj-$(CONFIG_VIDEO_MXC_EMMA_CAMERA) += mxc/capture/ +obj-$(CONFIG_VIDEO_MXC_CSI_CAMERA) += mxc/capture/ +obj-$(CONFIG_VIDEO_MXC_IPU_OUTPUT) += mxc/output/ +obj-$(CONFIG_VIDEO_MXC_IPUV1_WVGA_OUTPUT) += mxc/output/ +obj-$(CONFIG_VIDEO_MXC_EMMA_OUTPUT) += mxc/output/ +obj-$(CONFIG_VIDEO_MXC_OPL) += mxc/opl/ +obj-$(CONFIG_VIDEO_PXP) += pxp.o +obj-$(CONFIG_VIDEO_MXS_PXP) += mxs_pxp.o obj-$(CONFIG_VIDEO_CPIA) += cpia.o obj-$(CONFIG_VIDEO_CPIA_PP) += cpia_pp.o obj-$(CONFIG_VIDEO_CPIA_USB) += cpia_usb.o diff --git a/drivers/media/video/mxc/capture/Kconfig b/drivers/media/video/mxc/capture/Kconfig new file mode 100644 index 000000000000..1b352afebd87 --- /dev/null +++ b/drivers/media/video/mxc/capture/Kconfig @@ -0,0 +1,123 @@ +if VIDEO_MXC_CAMERA + +menu "MXC Camera/V4L2 PRP Features support" +config VIDEO_MXC_IPU_CAMERA + bool + depends on VIDEO_MXC_CAMERA && MXC_IPU + default y + +config VIDEO_MXC_EMMA_CAMERA + tristate "MX27 eMMA support" + depends on VIDEO_MXC_CAMERA && MXC_EMMA && FB_MXC_SYNC_PANEL + select VIDEO_MXC_OPL + default y + +config VIDEO_MXC_CSI_CAMERA + tristate "MX25 CSI camera support" + depends on !VIDEO_MXC_EMMA_CAMERA + +config VIDEO_MXC_CSI_DMA + bool "CSI-DMA Still Image Capture support" + depends on VIDEO_MXC_EMMA_CAMERA + default n + ---help--- + Use CSI-DMA method instead of CSI-PrP link to capture still image. This allows + to use less physical contiguous memory to capture big resolution still image. But + with this method the CSC (Color Space Conversion) and resize are not supported. + If unsure, say N. + +choice + prompt "Select Camera/TV Decoder" + default MXC_CAMERA_OV3640 + depends on VIDEO_MXC_CAMERA + +config MXC_CAMERA_MC521DA + tristate "Magnachip mc521da camera support" + select I2C_MXC + depends on VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the mc521da Camera with your MXC system, say Y here. + +config MXC_EMMA_CAMERA_MICRON111 + tristate "Micron mt9v111 camera support with eMMA" + select I2C_MXC + depends on VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the mt9v111 Camera with your MXC system, say Y here. + +config MXC_CAMERA_OV2640_EMMA + tristate "OmniVision ov2640 camera support with eMMA" + depends on VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the ov2640 Camera with your MXC system, say Y here. + +config MXC_CAMERA_MICRON111 + tristate "Micron mt9v111 camera support" + select I2C_MXC + depends on ! VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the mt9v111 Camera with your MXC system, say Y here. + +config MXC_CAMERA_OV2640 + tristate "OmniVision ov2640 camera support" + depends on !VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the ov2640 Camera with your MXC system, say Y here. + +config MXC_CAMERA_OV3640 + tristate "OmniVision ov3640 camera support" + depends on !VIDEO_MXC_EMMA_CAMERA + ---help--- + If you plan to use the ov3640 Camera with your MXC system, say Y here. + +config MXC_TVIN_ADV7180 + tristate "Analog Device adv7180 TV Decoder Input support" + depends on (MACH_MX35_3DS || MACH_MX51_3DS) + ---help--- + If you plan to use the adv7180 video decoder with your MXC system, say Y here. + +endchoice + +config MXC_IPU_PRP_VF_SDC + tristate "Pre-Processor VF SDC library" + depends on VIDEO_MXC_IPU_CAMERA && FB_MXC_SYNC_PANEL + default y + ---help--- + Use case PRP_VF_SDC: + Preprocessing image from smart sensor for viewfinder and + displaying it on synchronous display with SDC use case. + If SDC BG is selected, Rotation will not be supported. + CSI -> IC (PRP VF) -> MEM + MEM -> IC (ROT) -> MEM + MEM -> SDC (FG/BG) + +config MXC_IPU_PRP_VF_ADC + tristate "Pre-Processor VF ADC library" + depends on VIDEO_MXC_IPU_CAMERA && FB_MXC_ASYNC_PANEL + default y + ---help--- + Use case PRP_VF_ADC: + Preprocessing image from smart sensor for viewfinder and + displaying it on asynchronous display. + CSI -> IC (PRP VF) -> ADC2 + +config MXC_IPU_PRP_ENC + tristate "Pre-processor Encoder library" + depends on VIDEO_MXC_IPU_CAMERA + default y + ---help--- + Use case PRP_ENC: + Preprocessing image from smart sensor for encoder. + CSI -> IC (PRP ENC) -> MEM + +config MXC_IPU_CSI_ENC + tristate "IPU CSI Encoder library" + depends on VIDEO_MXC_IPU_CAMERA + default y + ---help--- + Use case IPU_CSI_ENC: + Get raw image with CSI from smart sensor for encoder. + CSI -> MEM +endmenu + +endif diff --git a/drivers/media/video/mxc/capture/Makefile b/drivers/media/video/mxc/capture/Makefile new file mode 100644 index 000000000000..03ff094171bf --- /dev/null +++ b/drivers/media/video/mxc/capture/Makefile @@ -0,0 +1,39 @@ +ifeq ($(CONFIG_VIDEO_MXC_IPU_CAMERA),y) + obj-$(CONFIG_VIDEO_MXC_CAMERA) += mxc_v4l2_capture.o + obj-$(CONFIG_MXC_IPU_PRP_VF_ADC) += ipu_prp_vf_adc.o + obj-$(CONFIG_MXC_IPU_PRP_VF_SDC) += ipu_prp_vf_sdc.o ipu_prp_vf_sdc_bg.o + obj-$(CONFIG_MXC_IPU_PRP_ENC) += ipu_prp_enc.o ipu_still.o + obj-$(CONFIG_MXC_IPU_CSI_ENC) += ipu_csi_enc.o ipu_still.o +endif + +obj-$(CONFIG_VIDEO_MXC_CSI_CAMERA) += fsl_csi.o csi_v4l2_capture.o + +mx27_capture-objs := mx27_prphw.o mx27_prpsw.o emma_v4l2_capture.o +obj-$(CONFIG_VIDEO_MXC_EMMA_CAMERA) += mx27_csi.o mx27_capture.o + +mc521da_camera-objs := mc521da.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_MC521DA) += mc521da_camera.o + +emma_mt9v111_camera-objs := emma_mt9v111.o sensor_clock.o +obj-$(CONFIG_MXC_EMMA_CAMERA_MICRON111) += emma_mt9v111_camera.o + +mt9v111_camera-objs := mt9v111.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_MICRON111) += mt9v111_camera.o + +hv7161_camera-objs := hv7161.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_HV7161) += hv7161_camera.o + +s5k3aaex_camera-objs := s5k3aaex.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_S5K3AAEX) += s5k3aaex_camera.o + +emma_ov2640_camera-objs := emma_ov2640.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_OV2640_EMMA) += emma_ov2640_camera.o + +ov2640_camera-objs := ov2640.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_OV2640) += ov2640_camera.o + +ov3640_camera-objs := ov3640.o sensor_clock.o +obj-$(CONFIG_MXC_CAMERA_OV3640) += ov3640_camera.o + +adv7180_tvin-objs := adv7180.o +obj-$(CONFIG_MXC_TVIN_ADV7180) += adv7180_tvin.o diff --git a/drivers/media/video/mxc/capture/adv7180.c b/drivers/media/video/mxc/capture/adv7180.c new file mode 100644 index 000000000000..527a0d1ad9fa --- /dev/null +++ b/drivers/media/video/mxc/capture/adv7180.c @@ -0,0 +1,1001 @@ +/* + * Copyright 2005-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file adv7180.c + * + * @brief Analog Device ADV7180 video decoder functions + * + * @ingroup Camera + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/wait.h> +#include <linux/videodev2.h> +#include <linux/workqueue.h> +#include <linux/regulator/consumer.h> +#include <media/v4l2-int-device.h> +#include <mach/hardware.h> +#include "mxc_v4l2_capture.h" + +static struct regulator *dvddio_regulator; +static struct regulator *dvdd_regulator; +static struct regulator *avdd_regulator; +static struct regulator *pvdd_regulator; +static struct mxc_tvin_platform_data *tvin_plat; + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +static int adv7180_probe(struct i2c_client *adapter, + const struct i2c_device_id *id); +static int adv7180_detach(struct i2c_client *client); + +static const struct i2c_device_id adv7180_id[] = { + {"adv7180", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, adv7180_id); + +static struct i2c_driver adv7180_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "adv7180", + }, + .probe = adv7180_probe, + .remove = adv7180_detach, + .id_table = adv7180_id, +}; + +/*! + * Maintains the information on the current state of the sesor. + */ +struct sensor { + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + v4l2_std_id std_id; +} adv7180_data; + +/*! List of input video formats supported. The video formats is corresponding + * with v4l2 id in video_fmt_t + */ +typedef enum { + ADV7180_NTSC = 0, /*!< Locked on (M) NTSC video signal. */ + ADV7180_PAL, /*!< (B, G, H, I, N)PAL video signal. */ + ADV7180_NOT_LOCKED, /*!< Not locked on a signal. */ +} video_fmt_idx; + +/*! Number of video standards supported (including 'not locked' signal). */ +#define ADV7180_STD_MAX (ADV7180_PAL + 1) + +/*! Video format structure. */ +typedef struct { + int v4l2_id; /*!< Video for linux ID. */ + char name[16]; /*!< Name (e.g., "NTSC", "PAL", etc.) */ + u16 raw_width; /*!< Raw width. */ + u16 raw_height; /*!< Raw height. */ + u16 active_width; /*!< Active width. */ + u16 active_height; /*!< Active height. */ +} video_fmt_t; + +/*! Description of video formats supported. + * + * PAL: raw=720x625, active=720x576. + * NTSC: raw=720x525, active=720x480. + */ +static video_fmt_t video_fmts[] = { + { /*! NTSC */ + .v4l2_id = V4L2_STD_NTSC, + .name = "NTSC", + .raw_width = 720, /* SENS_FRM_WIDTH */ + .raw_height = 525, /* SENS_FRM_HEIGHT */ + .active_width = 720, /* ACT_FRM_WIDTH plus 1 */ + .active_height = 480, /* ACT_FRM_WIDTH plus 1 */ + }, + { /*! (B, G, H, I, N) PAL */ + .v4l2_id = V4L2_STD_PAL, + .name = "PAL", + .raw_width = 720, + .raw_height = 625, + .active_width = 720, + .active_height = 576, + }, + { /*! Unlocked standard */ + .v4l2_id = V4L2_STD_ALL, + .name = "Autodetect", + .raw_width = 720, + .raw_height = 625, + .active_width = 720, + .active_height = 576, + }, +}; + +/*!* Standard index of ADV7180. */ +static video_fmt_idx video_idx = ADV7180_PAL; + +/*! @brief This mutex is used to provide mutual exclusion. + * + * Create a mutex that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * and variables that were defined above. + */ +static DECLARE_MUTEX(mutex); + +#define IF_NAME "adv7180" +#define ADV7180_INPUT_CTL 0x00 /* Input Control */ +#define ADV7180_STATUS_1 0x10 /* Status #1 */ +#define ADV7180_BRIGHTNESS 0x0a /* Brightness */ +#define ADV7180_IDENT 0x11 /* IDENT */ +#define ADV7180_VSYNC_FIELD_CTL_1 0x31 /* VSYNC Field Control #1 */ +#define ADV7180_MANUAL_WIN_CTL 0x3d /* Manual Window Control */ +#define ADV7180_SD_SATURATION_CB 0xe3 /* SD Saturation Cb */ +#define ADV7180_SD_SATURATION_CR 0xe4 /* SD Saturation Cr */ +#define ADV7180_PWR_MNG 0x0f /* Power Management */ + +/* supported controls */ +/* This hasn't been fully implemented yet. + * This is how it should work, though. */ +static struct v4l2_queryctrl adv7180_qctrl[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, /* check this value */ + .maximum = 255, /* check this value */ + .step = 1, /* check this value */ + .default_value = 127, /* check this value */ + .flags = 0, + }, { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, /* check this value */ + .maximum = 255, /* check this value */ + .step = 0x1, /* check this value */ + .default_value = 127, /* check this value */ + .flags = 0, + } +}; + +/*********************************************************************** + * I2C transfert. + ***********************************************************************/ + +/*! Read one register from a ADV7180 i2c slave device. + * + * @param *reg register in the device we wish to access. + * + * @return 0 if success, an error code otherwise. + */ +static inline int adv7180_read(u8 reg) +{ + int val; + val = i2c_smbus_read_byte_data(adv7180_data.i2c_client, reg); + if (val < 0) { + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:read reg error: reg=%2x \n", __func__, reg); + return -1; + } + return val; +} + +/*! Write one register of a ADV7180 i2c slave device. + * + * @param *reg register in the device we wish to access. + * + * @return 0 if success, an error code otherwise. + */ +static int adv7180_write_reg(u8 reg, u8 val) +{ + if (i2c_smbus_write_byte_data(adv7180_data.i2c_client, reg, val) < 0) { + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:write reg error:reg=%2x,val=%2x\n", __func__, + reg, val); + return -1; + } + return 0; +} + +/*********************************************************************** + * mxc_v4l2_capture interface. + ***********************************************************************/ + +/*! + * Return attributes of current video standard. + * Since this device autodetects the current standard, this function also + * sets the values that need to be changed if the standard changes. + * There is no set std equivalent function. + * + * @return None. + */ +static void adv7180_get_std(v4l2_std_id *std) +{ + int tmp; + int idx; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180_get_std\n"); + + /* Make sure power on */ + if (tvin_plat->pwdn) + tvin_plat->pwdn(0); + + /* Read the AD_RESULT to get the detect output video standard */ + tmp = adv7180_read(ADV7180_STATUS_1) & 0x70; + + down(&mutex); + if (tmp == 0x40) { + /* PAL */ + *std = V4L2_STD_PAL; + idx = ADV7180_PAL; + } else if (tmp == 0) { + /*NTSC*/ + *std = V4L2_STD_NTSC; + idx = ADV7180_NTSC; + } else { + *std = V4L2_STD_ALL; + idx = ADV7180_NOT_LOCKED; + dev_dbg(&adv7180_data.i2c_client->dev, + "Got invalid video standard! \n"); + } + up(&mutex); + + /* This assumes autodetect which this device uses. */ + if (*std != adv7180_data.std_id) { + video_idx = idx; + adv7180_data.std_id = *std; + adv7180_data.pix.width = video_fmts[video_idx].raw_width; + adv7180_data.pix.height = video_fmts[video_idx].raw_height; + } +} + +/*********************************************************************** + * IOCTL Functions from v4l2_int_ioctl_desc. + ***********************************************************************/ + +/*! + * ioctl_g_ifparm - V4L2 sensor interface handler for vidioc_int_g_ifparm_num + * s: pointer to standard V4L2 device structure + * p: pointer to standard V4L2 vidioc_int_g_ifparm_num ioctl structure + * + * Gets slave interface parameters. + * Calculates the required xclk value to support the requested + * clock parameters in p. This value is returned in the p + * parameter. + * + * vidioc_int_g_ifparm returns platform-specific information about the + * interface settings used by the sensor. + * + * Called on open. + */ +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_g_ifparm\n"); + + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + /* Initialize structure to 0s then set any non-0 values. */ + memset(p, 0, sizeof(*p)); + p->if_type = V4L2_IF_TYPE_BT656; /* This is the only possibility. */ + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.nobt_hs_inv = 1; + + /* ADV7180 has a dedicated clock so no clock settings needed. */ + + return 0; +} + +/*! + * Sets the camera power. + * + * s pointer to the camera device + * on if 1, power is to be turned on. 0 means power is to be turned off + * + * ioctl_s_power - V4L2 sensor interface handler for vidioc_int_s_power_num + * @s: pointer to standard V4L2 device structure + * @on: power state to which device is to be set + * + * Sets devices power state to requrested state, if possible. + * This is called on open, close, suspend and resume. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_s_power\n"); + + if (on && !sensor->on) { + gpio_sensor_active(); + + /* Make sure pwoer on */ + if (tvin_plat->pwdn) + tvin_plat->pwdn(0); + + if (adv7180_write_reg(ADV7180_PWR_MNG, 0) != 0) + return -EIO; + } else if (!on && sensor->on) { + if (adv7180_write_reg(ADV7180_PWR_MNG, 0x24) != 0) + return -EIO; + gpio_sensor_inactive(); + } + + sensor->on = on; + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_g_parm\n"); + + switch (a->type) { + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = sensor->streamcap.capability; + cparm->timeperframe = sensor->streamcap.timeperframe; + cparm->capturemode = sensor->streamcap.capturemode; + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + break; + + default: + pr_debug("ioctl_g_parm:type is unknown %d\n", a->type); + break; + } + + return 0; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + * + * This driver cannot change these settings. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_s_parm\n"); + + switch (a->type) { + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + break; + } + + return 0; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_g_fmt_cap\n"); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" Returning size of %dx%d\n", + sensor->pix.width, sensor->pix.height); + f->fmt.pix = sensor->pix; + break; + + case V4L2_BUF_TYPE_PRIVATE: { + v4l2_std_id std; + adv7180_get_std(&std); + f->fmt.pix.pixelformat = (u32)std; + } + break; + + default: + f->fmt.pix = sensor->pix; + break; + } + + return 0; +} + +/*! + * ioctl_queryctrl - V4L2 sensor interface handler for VIDIOC_QUERYCTRL ioctl + * @s: pointer to standard V4L2 device structure + * @qc: standard V4L2 VIDIOC_QUERYCTRL ioctl structure + * + * If the requested control is supported, returns the control information + * from the video_control[] array. Otherwise, returns -EINVAL if the + * control is not supported. + */ +static int ioctl_queryctrl(struct v4l2_int_device *s, + struct v4l2_queryctrl *qc) +{ + int i; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_queryctrl\n"); + + for (i = 0; i < ARRAY_SIZE(adv7180_qctrl); i++) + if (qc->id && qc->id == adv7180_qctrl[i].id) { + memcpy(qc, &(adv7180_qctrl[i]), + sizeof(*qc)); + return (0); + } + + return -EINVAL; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int ret = 0; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_g_ctrl\n"); + + /* Make sure power on */ + if (tvin_plat->pwdn) + tvin_plat->pwdn(0); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_BRIGHTNESS\n"); + adv7180_data.brightness = adv7180_read(ADV7180_BRIGHTNESS); + vc->value = adv7180_data.brightness; + break; + case V4L2_CID_CONTRAST: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_CONTRAST\n"); + vc->value = adv7180_data.contrast; + break; + case V4L2_CID_SATURATION: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_SATURATION\n"); + adv7180_data.saturation = adv7180_read(ADV7180_SD_SATURATION_CB); + vc->value = adv7180_data.saturation; + break; + case V4L2_CID_HUE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_HUE\n"); + vc->value = adv7180_data.hue; + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_RED_BALANCE\n"); + vc->value = adv7180_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_BLUE_BALANCE\n"); + vc->value = adv7180_data.blue; + break; + case V4L2_CID_GAMMA: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_EXPOSURE\n"); + vc->value = adv7180_data.ae_mode; + break; + case V4L2_CID_AUTOGAIN: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_VFLIP\n"); + break; + default: + dev_dbg(&adv7180_data.i2c_client->dev, + " Default case\n"); + vc->value = 0; + ret = -EPERM; + break; + } + + return ret; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + u8 tmp; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_s_ctrl\n"); + + /* Make sure power on */ + if (tvin_plat->pwdn) + tvin_plat->pwdn(0); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_BRIGHTNESS\n"); + tmp = vc->value; + adv7180_write_reg(ADV7180_BRIGHTNESS, tmp); + adv7180_data.brightness = vc->value; + break; + case V4L2_CID_CONTRAST: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_CONTRAST\n"); + break; + case V4L2_CID_SATURATION: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_SATURATION\n"); + tmp = vc->value; + adv7180_write_reg(ADV7180_SD_SATURATION_CB, tmp); + adv7180_write_reg(ADV7180_SD_SATURATION_CR, tmp); + adv7180_data.saturation = vc->value; + break; + case V4L2_CID_HUE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_HUE\n"); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_RED_BALANCE\n"); + break; + case V4L2_CID_BLUE_BALANCE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_BLUE_BALANCE\n"); + break; + case V4L2_CID_GAMMA: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_EXPOSURE\n"); + break; + case V4L2_CID_AUTOGAIN: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + dev_dbg(&adv7180_data.i2c_client->dev, + " V4L2_CID_VFLIP\n"); + break; + default: + dev_dbg(&adv7180_data.i2c_client->dev, + " Default case\n"); + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_init\n"); + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180:ioctl_dev_init\n"); + return 0; +} + +/*! + * This structure defines all the ioctls for this module. + */ +static struct v4l2_int_ioctl_desc adv7180_ioctl_desc[] = { + + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, + + /*! + * Delinitialise the dev. at slave detach. + * The complement of ioctl_dev_init. + */ +/* {vidioc_int_dev_exit_num, (v4l2_int_ioctl_func *)ioctl_dev_exit}, */ + + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *)ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *)ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *)ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *)ioctl_init}, + + /*! + * VIDIOC_ENUM_FMT ioctl for the CAPTURE buffer type. + */ +/* {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap}, */ + + /*! + * VIDIOC_TRY_FMT ioctl for the CAPTURE buffer type. + * This ioctl is used to negotiate the image capture size and + * pixel format without actually making it take effect. + */ +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap}, */ + + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_g_fmt_cap}, + + /*! + * If the requested format is supported, configures the HW to use that + * format, returns error code if format not supported or HW can't be + * correctly configured. + */ +/* {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_s_fmt_cap}, */ + + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *)ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *)ioctl_s_parm}, + {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *)ioctl_queryctrl}, + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *)ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *)ioctl_s_ctrl}, +}; + +static struct v4l2_int_slave adv7180_slave = { + .ioctls = adv7180_ioctl_desc, + .num_ioctls = ARRAY_SIZE(adv7180_ioctl_desc), +}; + +static struct v4l2_int_device adv7180_int_device = { + .module = THIS_MODULE, + .name = "adv7180", + .type = v4l2_int_type_slave, + .u = { + .slave = &adv7180_slave, + }, +}; + + +/*********************************************************************** + * I2C client and driver. + ***********************************************************************/ + +/*! ADV7180 Reset function. + * + * @return None. + */ +static void adv7180_hard_reset(void) +{ + dev_dbg(&adv7180_data.i2c_client->dev, + "In adv7180:adv7180_hard_reset\n"); + + /*! Driver works fine without explicit register + * initialization. Furthermore, initializations takes about 2 seconds + * at startup... + */ + + /*! Set YPbPr input on AIN1,4,5 and normal + * operations(autodection of all stds). + */ + adv7180_write_reg(ADV7180_INPUT_CTL, 0x09); + + /*! Datasheet recommends: */ + adv7180_write_reg(ADV7180_VSYNC_FIELD_CTL_1, 0x02); + adv7180_write_reg(ADV7180_MANUAL_WIN_CTL, 0xa2); +} + +/*! ADV7180 I2C attach function. + * + * @param *adapter struct i2c_adapter *. + * + * @return Error code indicating success or failure. + */ + +/*! + * ADV7180 I2C probe function. + * Function set in i2c_driver struct. + * Called by insmod. + * + * @param *adapter I2C adapter descriptor. + * + * @return Error code indicating success or failure. + */ +static int adv7180_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rev_id; + int ret = 0; + tvin_plat = client->dev.platform_data; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180_probe\n"); + + if (tvin_plat->dvddio_reg) { + dvddio_regulator = + regulator_get(&client->dev, tvin_plat->dvddio_reg); + if (!IS_ERR_VALUE((unsigned long)dvddio_regulator)) { + regulator_set_voltage(dvddio_regulator, 3300000, 3300000); + if (regulator_enable(dvddio_regulator) != 0) + return -ENODEV; + } + } + + if (tvin_plat->dvdd_reg) { + dvdd_regulator = + regulator_get(&client->dev, tvin_plat->dvdd_reg); + if (!IS_ERR_VALUE((unsigned long)dvdd_regulator)) { + regulator_set_voltage(dvdd_regulator, 1800000, 1800000); + if (regulator_enable(dvdd_regulator) != 0) + return -ENODEV; + } + } + + if (tvin_plat->avdd_reg) { + avdd_regulator = + regulator_get(&client->dev, tvin_plat->avdd_reg); + if (!IS_ERR_VALUE((unsigned long)avdd_regulator)) { + regulator_set_voltage(avdd_regulator, 1800000, 1800000); + if (regulator_enable(avdd_regulator) != 0) + return -ENODEV; + } + } + + if (tvin_plat->pvdd_reg) { + pvdd_regulator = + regulator_get(&client->dev, tvin_plat->pvdd_reg); + if (!IS_ERR_VALUE((unsigned long)pvdd_regulator)) { + regulator_set_voltage(pvdd_regulator, 1800000, 1800000); + if (regulator_enable(pvdd_regulator) != 0) + return -ENODEV; + } + } + + + if (tvin_plat->reset) + tvin_plat->reset(); + + if (tvin_plat->pwdn) + tvin_plat->pwdn(0); + + msleep(1); + + /* Set initial values for the sensor struct. */ + memset(&adv7180_data, 0, sizeof(adv7180_data)); + adv7180_data.i2c_client = client; + adv7180_data.streamcap.timeperframe.denominator = 30; + adv7180_data.streamcap.timeperframe.numerator = 1; + adv7180_data.std_id = V4L2_STD_ALL; + video_idx = ADV7180_NOT_LOCKED; + adv7180_data.pix.width = video_fmts[video_idx].raw_width; + adv7180_data.pix.height = video_fmts[video_idx].raw_height; + adv7180_data.pix.pixelformat = V4L2_PIX_FMT_UYVY; /* YUV422 */ + adv7180_data.pix.priv = 1; /* 1 is used to indicate TV in */ + adv7180_data.on = true; + + gpio_sensor_active(); + + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:adv7180 probe i2c address is 0x%02X \n", + __func__, adv7180_data.i2c_client->addr); + + /*! Read the revision ID of the tvin chip */ + rev_id = adv7180_read(ADV7180_IDENT); + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:Analog Device adv7%2X0 detected! \n", __func__, + rev_id); + + /*! ADV7180 initialization. */ + adv7180_hard_reset(); + + pr_debug(" type is %d (expect %d)\n", + adv7180_int_device.type, v4l2_int_type_slave); + pr_debug(" num ioctls is %d\n", + adv7180_int_device.u.slave->num_ioctls); + + /* This function attaches this structure to the /dev/video0 device. + * The pointer in priv points to the mt9v111_data structure here.*/ + adv7180_int_device.priv = &adv7180_data; + ret = v4l2_int_device_register(&adv7180_int_device); + + return ret; +} + +/*! + * ADV7180 I2C detach function. + * Called on rmmod. + * + * @param *client struct i2c_client*. + * + * @return Error code indicating success or failure. + */ +static int adv7180_detach(struct i2c_client *client) +{ + struct mxc_tvin_platform_data *plat_data = client->dev.platform_data; + + dev_dbg(&adv7180_data.i2c_client->dev, + "%s:Removing %s video decoder @ 0x%02X from adapter %s \n", + __func__, IF_NAME, client->addr << 1, client->adapter->name); + + if (plat_data->pwdn) + plat_data->pwdn(1); + + if (dvddio_regulator) { + regulator_disable(dvddio_regulator); + regulator_put(dvddio_regulator); + } + + if (dvdd_regulator) { + regulator_disable(dvdd_regulator); + regulator_put(dvdd_regulator); + } + + if (avdd_regulator) { + regulator_disable(avdd_regulator); + regulator_put(avdd_regulator); + } + + if (pvdd_regulator) { + regulator_disable(pvdd_regulator); + regulator_put(pvdd_regulator); + } + + v4l2_int_device_unregister(&adv7180_int_device); + + return 0; +} + +/*! + * ADV7180 init function. + * Called on insmod. + * + * @return Error code indicating success or failure. + */ +static __init int adv7180_init(void) +{ + u8 err = 0; + + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180_init\n"); + + /* Tells the i2c driver what functions to call for this driver. */ + err = i2c_add_driver(&adv7180_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * ADV7180 cleanup function. + * Called on rmmod. + * + * @return Error code indicating success or failure. + */ +static void __exit adv7180_clean(void) +{ + dev_dbg(&adv7180_data.i2c_client->dev, "In adv7180_clean\n"); + i2c_del_driver(&adv7180_i2c_driver); + gpio_sensor_inactive(); +} + +module_init(adv7180_init); +module_exit(adv7180_clean); + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Anolog Device ADV7180 video decoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/csi_v4l2_capture.c b/drivers/media/video/mxc/capture/csi_v4l2_capture.c new file mode 100644 index 000000000000..cf224e0673f0 --- /dev/null +++ b/drivers/media/video/mxc/capture/csi_v4l2_capture.c @@ -0,0 +1,1466 @@ +/* + * Copyright 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/capture/csi_v4l2_capture.c + * This file is derived from mxc_v4l2_capture.c + * + * @brief MX25 Video For Linux 2 driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/io.h> +#include <linux/semaphore.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/types.h> +#include <linux/fb.h> +#include <linux/dma-mapping.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-int-device.h> +#include <linux/mxcfb.h> +#include "mxc_v4l2_capture.h" +#include "fsl_csi.h" + +static int video_nr = -1; +static cam_data *g_cam; + +static int csi_v4l2_master_attach(struct v4l2_int_device *slave); +static void csi_v4l2_master_detach(struct v4l2_int_device *slave); +static u8 camera_power(cam_data *cam, bool cameraOn); + +/*! Information about this driver. */ +static struct v4l2_int_master csi_v4l2_master = { + .attach = csi_v4l2_master_attach, + .detach = csi_v4l2_master_detach, +}; + +static struct v4l2_int_device csi_v4l2_int_device = { + .module = THIS_MODULE, + .name = "csi_v4l2_cap", + .type = v4l2_int_type_master, + .u = { + .master = &csi_v4l2_master, + }, +}; + +/*! + * Camera V4l2 callback function. + * + * @param mask u32 + * @param dev void device structure + * + * @return none + */ +static void camera_callback(u32 mask, void *dev) +{ + struct mxc_v4l_frame *done_frame; + struct mxc_v4l_frame *ready_frame; + cam_data *cam; + + cam = (cam_data *) dev; + if (cam == NULL) + return; + + if (list_empty(&cam->working_q)) { + pr_err("ERROR: v4l2 capture: %s: " + "working queue empty\n", __func__); + return; + } + + done_frame = + list_entry(cam->working_q.next, struct mxc_v4l_frame, queue); + if (done_frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + done_frame->buffer.flags |= V4L2_BUF_FLAG_DONE; + done_frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + if (list_empty(&cam->ready_q)) { + cam->skip_frame++; + } else { + ready_frame = list_entry(cam->ready_q.next, + struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&ready_frame->queue, &cam->working_q); + + if (cam->ping_pong_csi == 1) { + __raw_writel(cam->frame[ready_frame->index]. + paddress, CSI_CSIDMASA_FB1); + } else { + __raw_writel(cam->frame[ready_frame->index]. + paddress, CSI_CSIDMASA_FB2); + } + } + + /* Added to the done queue */ + list_del(cam->working_q.next); + list_add_tail(&done_frame->queue, &cam->done_q); + cam->enc_counter++; + wake_up_interruptible(&cam->enc_queue); + } else { + pr_err("ERROR: v4l2 capture: %s: " + "buffer not queued\n", __func__); + } + + return; +} + +/*! + * Make csi ready for capture image. + * + * @param cam structure cam_data * + * + * @return status 0 success + */ +static int csi_cap_image(cam_data *cam) +{ + unsigned int value; + + value = __raw_readl(CSI_CSICR3); + __raw_writel(value | BIT_DMA_REFLASH_RFF | BIT_FRMCNT_RST, CSI_CSICR3); + value = __raw_readl(CSI_CSISR); + __raw_writel(value, CSI_CSISR); + + return 0; +} + +/*************************************************************************** + * Functions for handling Frame buffers. + **************************************************************************/ + +/*! + * Free frame buffers + * + * @param cam Structure cam_data * + * + * @return status 0 success. + */ +static int csi_free_frame_buf(cam_data *cam) +{ + int i; + + pr_debug("MVC: In %s\n", __func__); + + for (i = 0; i < FRAME_NUM; i++) { + if (cam->frame[i].vaddress != 0) { + dma_free_coherent(0, cam->frame[i].buffer.length, + cam->frame[i].vaddress, + cam->frame[i].paddress); + cam->frame[i].vaddress = 0; + } + } + + return 0; +} + +/*! + * Allocate frame buffers + * + * @param cam Structure cam_data * + * @param count int number of buffer need to allocated + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int csi_allocate_frame_buf(cam_data *cam, int count) +{ + int i; + + pr_debug("In MVC:%s- size=%d\n", + __func__, cam->v2f.fmt.pix.sizeimage); + for (i = 0; i < count; i++) { + cam->frame[i].vaddress = dma_alloc_coherent(0, PAGE_ALIGN + (cam->v2f.fmt. + pix.sizeimage), + &cam->frame[i]. + paddress, + GFP_DMA | + GFP_KERNEL); + if (cam->frame[i].vaddress == 0) { + pr_err("ERROR: v4l2 capture: " + "%s failed.\n", __func__); + csi_free_frame_buf(cam); + return -ENOBUFS; + } + cam->frame[i].buffer.index = i; + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + cam->frame[i].buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->frame[i].buffer.length = PAGE_ALIGN(cam->v2f.fmt. + pix.sizeimage); + cam->frame[i].buffer.memory = V4L2_MEMORY_MMAP; + cam->frame[i].buffer.m.offset = cam->frame[i].paddress; + cam->frame[i].index = i; + } + + return 0; +} + +/*! + * Free frame buffers status + * + * @param cam Structure cam_data * + * + * @return none + */ +static void csi_free_frames(cam_data *cam) +{ + int i; + + pr_debug("In MVC: %s\n", __func__); + + for (i = 0; i < FRAME_NUM; i++) + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + return; +} + +/*! + * Return the buffer status + * + * @param cam Structure cam_data * + * @param buf Structure v4l2_buffer * + * + * @return status 0 success, EINVAL failed. + */ +static int csi_v4l2_buffer_status(cam_data *cam, struct v4l2_buffer *buf) +{ + pr_debug("In MVC: %s\n", __func__); + + if (buf->index < 0 || buf->index >= FRAME_NUM) { + pr_err("ERROR: v4l2 capture: %s buffers " + "not allocated\n", __func__); + return -EINVAL; + } + + memcpy(buf, &(cam->frame[buf->index].buffer), sizeof(*buf)); + + return 0; +} + +/*! + * Indicates whether the palette is supported. + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_UYVY or V4L2_PIX_FMT_YUV420 + * + * @return 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return (palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_UYVY) || (palette == V4L2_PIX_FMT_YUV420); +} + +/*! + * Start stream I/O + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int csi_streamon(cam_data *cam) +{ + struct mxc_v4l_frame *frame; + + pr_debug("In MVC: %s\n", __func__); + + if (NULL == cam) { + pr_err("ERROR: v4l2 capture: %s cam parameter is NULL\n", + __func__); + return -1; + } + + /* move the frame from readyq to workingq */ + if (list_empty(&cam->ready_q)) { + pr_err("ERROR: v4l2 capture: %s: " + "ready_q queue empty\n", __func__); + return -1; + } + frame = list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + __raw_writel(cam->frame[frame->index].paddress, CSI_CSIDMASA_FB1); + + if (list_empty(&cam->ready_q)) { + pr_err("ERROR: v4l2 capture: %s: " + "ready_q queue empty\n", __func__); + return -1; + } + frame = list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + __raw_writel(cam->frame[frame->index].paddress, CSI_CSIDMASA_FB2); + + cam->capture_pid = current->pid; + cam->capture_on = true; + csi_cap_image(cam); + csi_enable_int(1); + + return 0; +} + +/*! + * Stop stream I/O + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int csi_streamoff(cam_data *cam) +{ + unsigned int cr3; + + pr_debug("In MVC: %s\n", __func__); + + if (cam->capture_on == false) + return 0; + + csi_disable_int(); + cam->capture_on = false; + + /* set CSI_CSIDMASA_FB1 and CSI_CSIDMASA_FB2 to default value */ + __raw_writel(0, CSI_CSIDMASA_FB1); + __raw_writel(0, CSI_CSIDMASA_FB2); + cr3 = __raw_readl(CSI_CSICR3); + __raw_writel(cr3 | BIT_DMA_REFLASH_RFF, CSI_CSICR3); + + csi_free_frames(cam); + csi_free_frame_buf(cam); + + return 0; +} + +/*! + * start the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int start_preview(cam_data *cam) +{ + unsigned long fb_addr = (unsigned long)cam->v4l2_fb.base; + + __raw_writel(fb_addr, CSI_CSIDMASA_FB1); + __raw_writel(fb_addr, CSI_CSIDMASA_FB2); + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_DMA_REFLASH_RFF, CSI_CSICR3); + + csi_enable_int(0); + + return 0; +} + +/*! + * shut down the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int stop_preview(cam_data *cam) +{ + csi_disable_int(); + + /* set CSI_CSIDMASA_FB1 and CSI_CSIDMASA_FB2 to default value */ + __raw_writel(0, CSI_CSIDMASA_FB1); + __raw_writel(0, CSI_CSIDMASA_FB2); + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_DMA_REFLASH_RFF, CSI_CSICR3); + + return 0; +} + +/*************************************************************************** + * VIDIOC Functions. + **************************************************************************/ + +/*! + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int csi_v4l2_g_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + f->fmt.pix = cam->v2f.fmt.pix; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_OVERLAY\n"); + f->fmt.win = cam->win; + break; + default: + pr_debug(" type is invalid\n"); + retval = -EINVAL; + } + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + + return retval; +} + +/*! + * V4L2 - csi_v4l2_s_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int csi_v4l2_s_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + int size = 0; + int bytesperline = 0; + int *width, *height; + + pr_debug("In MVC: %s\n", __func__); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type=V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_err("ERROR: v4l2 capture: %s: format " + "not supported\n", __func__); + return -EINVAL; + } + + /* Handle case where size requested is larger than cuurent + * camera setting. */ + if ((f->fmt.pix.width > cam->crop_bounds.width) + || (f->fmt.pix.height > cam->crop_bounds.height)) { + /* Need the logic here, calling vidioc_s_param if + * camera can change. */ + pr_debug("csi_v4l2_s_fmt size changed\n"); + } + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + height = &f->fmt.pix.width; + width = &f->fmt.pix.height; + } else { + width = &f->fmt.pix.width; + height = &f->fmt.pix.height; + } + + if ((cam->crop_bounds.width / *width > 8) || + ((cam->crop_bounds.width / *width == 8) && + (cam->crop_bounds.width % *width))) { + *width = cam->crop_bounds.width / 8; + if (*width % 8) + *width += 8 - *width % 8; + pr_err("ERROR: v4l2 capture: width exceeds limit " + "resize to %d.\n", *width); + } + + if ((cam->crop_bounds.height / *height > 8) || + ((cam->crop_bounds.height / *height == 8) && + (cam->crop_bounds.height % *height))) { + *height = cam->crop_bounds.height / 8; + if (*height % 8) + *height += 8 - *height % 8; + pr_err("ERROR: v4l2 capture: height exceeds limit " + "resize to %d.\n", *height); + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_RGB565: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + csi_set_16bit_imagpara(f->fmt.pix.width, + f->fmt.pix.height); + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_UYVY: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + csi_set_16bit_imagpara(f->fmt.pix.width, + f->fmt.pix.height); + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_YUV420: + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + csi_set_12bit_imagpara(f->fmt.pix.width, + f->fmt.pix.height); + bytesperline = f->fmt.pix.width; + break; + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_RGB24: + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_NV12: + default: + pr_debug(" case not supported\n"); + break; + } + + if (f->fmt.pix.bytesperline < bytesperline) + f->fmt.pix.bytesperline = bytesperline; + else + bytesperline = f->fmt.pix.bytesperline; + + if (f->fmt.pix.sizeimage < size) + f->fmt.pix.sizeimage = size; + else + size = f->fmt.pix.sizeimage; + + cam->v2f.fmt.pix = f->fmt.pix; + + if (cam->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&cam->offset, + (void *)cam->v2f.fmt.pix.priv, + sizeof(cam->offset))) { + retval = -EFAULT; + break; + } + } + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + pr_debug(" type=V4L2_BUF_TYPE_VIDEO_OVERLAY\n"); + cam->win = f->fmt.win; + break; + default: + retval = -EINVAL; + } + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + + return retval; +} + +/*! + * V4L2 - csi_v4l2_s_param function + * Allows setting of capturemode and frame rate. + * + * @param cam structure cam_data * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int csi_v4l2_s_param(cam_data *cam, struct v4l2_streamparm *parm) +{ + struct v4l2_ifparm ifparm; + struct v4l2_format cam_fmt; + struct v4l2_streamparm currentparm; + int err = 0; + + pr_debug("In %s\n", __func__); + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_err(KERN_ERR "%s invalid type\n", __func__); + return -EINVAL; + } + + /* Stop the viewfinder */ + if (cam->overlay_on == true) + stop_preview(cam); + + currentparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* First check that this device can support the changes requested. */ + err = vidioc_int_g_parm(cam->sensor, ¤tparm); + if (err) { + pr_err("%s: vidioc_int_g_parm returned an error %d\n", + __func__, err); + goto exit; + } + + pr_debug(" Current capabilities are %x\n", + currentparm.parm.capture.capability); + pr_debug(" Current capturemode is %d change to %d\n", + currentparm.parm.capture.capturemode, + parm->parm.capture.capturemode); + pr_debug(" Current framerate is %d change to %d\n", + currentparm.parm.capture.timeperframe.denominator, + parm->parm.capture.timeperframe.denominator); + + err = vidioc_int_s_parm(cam->sensor, parm); + if (err) { + pr_err("%s: vidioc_int_s_parm returned an error %d\n", + __func__, err); + goto exit; + } + + vidioc_int_g_ifparm(cam->sensor, &ifparm); + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + pr_debug(" g_fmt_cap returns widthxheight of input as %d x %d\n", + cam_fmt.fmt.pix.width, cam_fmt.fmt.pix.height); + +exit: + return err; +} + +/*! + * Dequeue one V4L capture buffer + * + * @param cam structure cam_data * + * @param buf structure v4l2_buffer * + * + * @return status 0 success, EINVAL invalid frame number + * ETIME timeout, ERESTARTSYS interrupted by user + */ +static int csi_v4l_dqueue(cam_data *cam, struct v4l2_buffer *buf) +{ + int retval = 0; + struct mxc_v4l_frame *frame; + unsigned long lock_flags; + + if (!wait_event_interruptible_timeout(cam->enc_queue, + cam->enc_counter != 0, 10 * HZ)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_dqueue timeout " + "enc_counter %x\n", cam->enc_counter); + return -ETIME; + } else if (signal_pending(current)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_dqueue() " + "interrupt received\n"); + return -ERESTARTSYS; + } + + spin_lock_irqsave(&cam->dqueue_int_lock, lock_flags); + + cam->enc_counter--; + + frame = list_entry(cam->done_q.next, struct mxc_v4l_frame, queue); + list_del(cam->done_q.next); + + if (frame->buffer.flags & V4L2_BUF_FLAG_DONE) { + frame->buffer.flags &= ~V4L2_BUF_FLAG_DONE; + } else if (frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + pr_err("ERROR: v4l2 capture: VIDIOC_DQBUF: " + "Buffer not filled.\n"); + frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } else if ((frame->buffer.flags & 0x7) == V4L2_BUF_FLAG_MAPPED) { + pr_err("ERROR: v4l2 capture: VIDIOC_DQBUF: " + "Buffer not queued.\n"); + retval = -EINVAL; + } + + spin_unlock_irqrestore(&cam->dqueue_int_lock, lock_flags); + + buf->bytesused = cam->v2f.fmt.pix.sizeimage; + buf->index = frame->index; + buf->flags = frame->buffer.flags; + buf->m = cam->frame[frame->index].buffer.m; + + return retval; +} + +/*! + * V4L interface - open function + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int csi_v4l_open(struct file *file) +{ + struct v4l2_ifparm ifparm; + struct v4l2_format cam_fmt; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int err = 0; + + pr_debug(" device name is %s\n", dev->name); + + if (!cam) { + pr_err("ERROR: v4l2 capture: Internal error, " + "cam_data not found!\n"); + return -EBADF; + } + + down(&cam->busy_lock); + err = 0; + if (signal_pending(current)) + goto oops; + + if (cam->open_count++ == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + vidioc_int_g_ifparm(cam->sensor, &ifparm); + + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + csi_enable_mclk(CSI_MCLK_I2C, true, true); + vidioc_int_init(cam->sensor); + } + + file->private_data = dev; + +oops: + up(&cam->busy_lock); + return err; +} + +/*! + * V4L interface - close function + * + * @param file struct file * + * + * @return 0 success + */ +static int csi_v4l_close(struct file *file) +{ + struct video_device *dev = video_devdata(file); + int err = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("In MVC:%s\n", __func__); + + if (!cam) { + pr_err("ERROR: v4l2 capture: Internal error, " + "cam_data not found!\n"); + return -EBADF; + } + + /* for the case somebody hit the ctrl C */ + if (cam->overlay_pid == current->pid) { + err = stop_preview(cam); + cam->overlay_on = false; + } + + if (--cam->open_count == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + file->private_data = NULL; + csi_enable_mclk(CSI_MCLK_I2C, false, false); + } + + return err; +} + +/* + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t csi_v4l_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + int err = 0; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Stop the viewfinder */ + if (cam->overlay_on == true) + stop_preview(cam); + + if (cam->still_buf_vaddr == NULL) { + cam->still_buf_vaddr = dma_alloc_coherent(0, + PAGE_ALIGN + (cam->v2f.fmt. + pix.sizeimage), + &cam-> + still_buf[0], + GFP_DMA | GFP_KERNEL); + if (cam->still_buf_vaddr == NULL) { + pr_err("alloc dma memory failed\n"); + return -ENOMEM; + } + cam->still_counter = 0; + __raw_writel(cam->still_buf[0], CSI_CSIDMASA_FB2); + __raw_writel(cam->still_buf[0], CSI_CSIDMASA_FB1); + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_DMA_REFLASH_RFF, + CSI_CSICR3); + __raw_writel(__raw_readl(CSI_CSISR), CSI_CSISR); + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_FRMCNT_RST, + CSI_CSICR3); + csi_enable_int(1); + } + + wait_event_interruptible(cam->still_queue, cam->still_counter); + csi_disable_int(); + err = copy_to_user(buf, cam->still_buf_vaddr, + cam->v2f.fmt.pix.sizeimage); + + if (cam->still_buf_vaddr != NULL) { + dma_free_coherent(0, PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + cam->still_buf_vaddr, cam->still_buf[0]); + cam->still_buf[0] = 0; + cam->still_buf_vaddr = NULL; + } + + if (cam->overlay_on == true) + start_preview(cam); + + up(&cam->busy_lock); + if (err < 0) + return err; + + return cam->v2f.fmt.pix.sizeimage - err; +} + +/*! + * V4L interface - ioctl function + * + * @param file struct file* + * + * @param ioctlnr unsigned int + * + * @param arg void* + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static long csi_v4l_do_ioctl(struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int retval = 0; + unsigned long lock_flags; + + pr_debug("In MVC: %s, %x\n", __func__, ioctlnr); + wait_event_interruptible(cam->power_queue, cam->low_power == false); + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + /*! + * V4l2 VIDIOC_G_FMT ioctl + */ + case VIDIOC_G_FMT:{ + struct v4l2_format *gf = arg; + pr_debug(" case VIDIOC_G_FMT\n"); + retval = csi_v4l2_g_fmt(cam, gf); + break; + } + + /*! + * V4l2 VIDIOC_S_FMT ioctl + */ + case VIDIOC_S_FMT:{ + struct v4l2_format *sf = arg; + pr_debug(" case VIDIOC_S_FMT\n"); + retval = csi_v4l2_s_fmt(cam, sf); + vidioc_int_s_fmt_cap(cam->sensor, sf); + break; + } + + /*! + * V4l2 VIDIOC_OVERLAY ioctl + */ + case VIDIOC_OVERLAY:{ + int *on = arg; + pr_debug(" case VIDIOC_OVERLAY\n"); + if (*on) { + cam->overlay_on = true; + cam->overlay_pid = current->pid; + start_preview(cam); + } + if (!*on) { + stop_preview(cam); + cam->overlay_on = false; + } + break; + } + + /*! + * V4l2 VIDIOC_G_FBUF ioctl + */ + case VIDIOC_G_FBUF:{ + struct v4l2_framebuffer *fb = arg; + *fb = cam->v4l2_fb; + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + break; + } + + /*! + * V4l2 VIDIOC_S_FBUF ioctl + */ + case VIDIOC_S_FBUF:{ + struct v4l2_framebuffer *fb = arg; + cam->v4l2_fb = *fb; + break; + } + + case VIDIOC_G_PARM:{ + struct v4l2_streamparm *parm = arg; + pr_debug(" case VIDIOC_G_PARM\n"); + vidioc_int_g_parm(cam->sensor, parm); + break; + } + + case VIDIOC_S_PARM:{ + struct v4l2_streamparm *parm = arg; + pr_debug(" case VIDIOC_S_PARM\n"); + retval = csi_v4l2_s_param(cam, parm); + break; + } + + case VIDIOC_QUERYCAP:{ + struct v4l2_capability *cap = arg; + pr_debug(" case VIDIOC_QUERYCAP\n"); + strcpy(cap->driver, "csi_v4l2"); + cap->version = KERNEL_VERSION(0, 1, 11); + cap->capabilities = V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_VIDEO_OUTPUT_OVERLAY | V4L2_CAP_READWRITE; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + break; + } + + case VIDIOC_S_CROP: + pr_debug(" case not supported\n"); + break; + + case VIDIOC_REQBUFS: { + struct v4l2_requestbuffers *req = arg; + pr_debug(" case VIDIOC_REQBUFS\n"); + + if (req->count > FRAME_NUM) { + pr_err("ERROR: v4l2 capture: VIDIOC_REQBUFS: " + "not enough buffers\n"); + req->count = FRAME_NUM; + } + + if ((req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (req->memory != V4L2_MEMORY_MMAP)) { + pr_err("ERROR: v4l2 capture: VIDIOC_REQBUFS: " + "wrong buffer type\n"); + retval = -EINVAL; + break; + } + + csi_streamoff(cam); + csi_free_frame_buf(cam); + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + retval = csi_allocate_frame_buf(cam, req->count); + break; + } + + case VIDIOC_QUERYBUF: { + struct v4l2_buffer *buf = arg; + int index = buf->index; + pr_debug(" case VIDIOC_QUERYBUF\n"); + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + retval = -EINVAL; + break; + } + + memset(buf, 0, sizeof(buf)); + buf->index = index; + retval = csi_v4l2_buffer_status(cam, buf); + break; + } + + case VIDIOC_QBUF: { + struct v4l2_buffer *buf = arg; + int index = buf->index; + pr_debug(" case VIDIOC_QBUF\n"); + + spin_lock_irqsave(&cam->queue_int_lock, lock_flags); + cam->frame[index].buffer.m.offset = buf->m.offset; + if ((cam->frame[index].buffer.flags & 0x7) == + V4L2_BUF_FLAG_MAPPED) { + cam->frame[index].buffer.flags |= V4L2_BUF_FLAG_QUEUED; + if (cam->skip_frame > 0) { + list_add_tail(&cam->frame[index].queue, + &cam->working_q); + cam->skip_frame = 0; + + if (cam->ping_pong_csi == 1) { + __raw_writel(cam->frame[index].paddress, + CSI_CSIDMASA_FB1); + } else { + __raw_writel(cam->frame[index].paddress, + CSI_CSIDMASA_FB2); + } + } else { + list_add_tail(&cam->frame[index].queue, + &cam->ready_q); + } + } else if (cam->frame[index].buffer.flags & + V4L2_BUF_FLAG_QUEUED) { + pr_err("ERROR: v4l2 capture: VIDIOC_QBUF: " + "buffer already queued\n"); + retval = -EINVAL; + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_DONE) { + pr_err("ERROR: v4l2 capture: VIDIOC_QBUF: " + "overwrite done buffer.\n"); + cam->frame[index].buffer.flags &= + ~V4L2_BUF_FLAG_DONE; + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } + buf->flags = cam->frame[index].buffer.flags; + spin_unlock_irqrestore(&cam->queue_int_lock, lock_flags); + + break; + } + + case VIDIOC_DQBUF: { + struct v4l2_buffer *buf = arg; + pr_debug(" case VIDIOC_DQBUF\n"); + + retval = csi_v4l_dqueue(cam, buf); + + break; + } + + case VIDIOC_STREAMON: { + pr_debug(" case VIDIOC_STREAMON\n"); + retval = csi_streamon(cam); + break; + } + + case VIDIOC_STREAMOFF: { + pr_debug(" case VIDIOC_STREAMOFF\n"); + retval = csi_streamoff(cam); + break; + } + + case VIDIOC_S_CTRL: + case VIDIOC_G_STD: + case VIDIOC_G_OUTPUT: + case VIDIOC_S_OUTPUT: + case VIDIOC_ENUMSTD: + case VIDIOC_G_CROP: + case VIDIOC_CROPCAP: + case VIDIOC_S_STD: + case VIDIOC_G_CTRL: + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_ENUMINPUT: + case VIDIOC_G_INPUT: + case VIDIOC_S_INPUT: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + case VIDIOC_ENUMOUTPUT: + default: + pr_debug(" case not supported\n"); + retval = -EINVAL; + break; + } + + up(&cam->busy_lock); + return retval; +} + +/* + * V4L interface - ioctl function + * + * @return None + */ +static long csi_v4l_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(file, cmd, arg, csi_v4l_do_ioctl); +} + +/*! + * V4L interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int csi_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = video_devdata(file); + unsigned long size; + int res = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("%s\n", __func__); + pr_debug("\npgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_err("ERROR: v4l2 capture: %s : " + "remap_pfn_range failed\n", __func__); + res = -ENOBUFS; + goto csi_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + +csi_mmap_exit: + up(&cam->busy_lock); + return res; +} + +/*! + * This structure defines the functions to be called in this driver. + */ +static struct v4l2_file_operations csi_v4l_fops = { + .owner = THIS_MODULE, + .open = csi_v4l_open, + .release = csi_v4l_close, + .read = csi_v4l_read, + .ioctl = csi_v4l_ioctl, + .mmap = csi_mmap, +}; + +static struct video_device csi_v4l_template = { + .name = "Mx25 Camera", + .fops = &csi_v4l_fops, + .release = video_device_release, +}; + +/*! + * This function can be used to release any platform data on closing. + */ +static void camera_platform_release(struct device *device) +{ +} + +/*! Device Definition for csi v4l2 device */ +static struct platform_device csi_v4l2_devices = { + .name = "csi_v4l2", + .dev = { + .release = camera_platform_release, + }, + .id = 0, +}; + +/*! + * initialize cam_data structure + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static void init_camera_struct(cam_data *cam) +{ + pr_debug("In MVC: %s\n", __func__); + + /* Default everything to 0 */ + memset(cam, 0, sizeof(cam_data)); + + init_MUTEX(&cam->param_lock); + init_MUTEX(&cam->busy_lock); + + cam->video_dev = video_device_alloc(); + if (cam->video_dev == NULL) + return; + + *(cam->video_dev) = csi_v4l_template; + + video_set_drvdata(cam->video_dev, cam); + dev_set_drvdata(&csi_v4l2_devices.dev, (void *)cam); + cam->video_dev->minor = -1; + + init_waitqueue_head(&cam->enc_queue); + init_waitqueue_head(&cam->still_queue); + + cam->streamparm.parm.capture.capturemode = 0; + + cam->standard.index = 0; + cam->standard.id = V4L2_STD_UNKNOWN; + cam->standard.frameperiod.denominator = 30; + cam->standard.frameperiod.numerator = 1; + cam->standard.framelines = 480; + cam->standard_autodetect = true; + cam->streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->streamparm.parm.capture.timeperframe = cam->standard.frameperiod; + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + cam->overlay_on = false; + cam->capture_on = false; + cam->skip_frame = 0; + cam->v4l2_fb.flags = V4L2_FBUF_FLAG_OVERLAY; + + cam->v2f.fmt.pix.sizeimage = 480 * 640 * 2; + cam->v2f.fmt.pix.bytesperline = 640 * 2; + cam->v2f.fmt.pix.width = 640; + cam->v2f.fmt.pix.height = 480; + cam->v2f.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY; + cam->win.w.width = 160; + cam->win.w.height = 160; + cam->win.w.left = 0; + cam->win.w.top = 0; + cam->still_counter = 0; + + cam->enc_callback = camera_callback; + csi_start_callback(cam); + init_waitqueue_head(&cam->power_queue); + spin_lock_init(&cam->queue_int_lock); + spin_lock_init(&cam->dqueue_int_lock); +} + +/*! + * camera_power function + * Turns Sensor power On/Off + * + * @param cam cam data struct + * @param cameraOn true to turn camera on, false to turn off power. + * + * @return status + */ +static u8 camera_power(cam_data *cam, bool cameraOn) +{ + pr_debug("In MVC: %s on=%d\n", __func__, cameraOn); + + if (cameraOn == true) { + csi_enable_mclk(CSI_MCLK_I2C, true, true); + vidioc_int_s_power(cam->sensor, 1); + } else { + csi_enable_mclk(CSI_MCLK_I2C, false, false); + vidioc_int_s_power(cam->sensor, 0); + } + return 0; +} + +/*! + * This function is called to put the sensor in a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure used to give information on which I2C + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure. + */ +static int csi_v4l2_suspend(struct platform_device *pdev, pm_message_t state) +{ + cam_data *cam = platform_get_drvdata(pdev); + + pr_debug("In MVC: %s\n", __func__); + + if (cam == NULL) + return -1; + + cam->low_power = true; + + if (cam->overlay_on == true) + stop_preview(cam); + + camera_power(cam, false); + + return 0; +} + +/*! + * This function is called to bring the sensor back from a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int csi_v4l2_resume(struct platform_device *pdev) +{ + cam_data *cam = platform_get_drvdata(pdev); + + pr_debug("In MVC: %s\n", __func__); + + if (cam == NULL) + return -1; + + cam->low_power = false; + wake_up_interruptible(&cam->power_queue); + camera_power(cam, true); + + if (cam->overlay_on == true) + start_preview(cam); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver csi_v4l2_driver = { + .driver = { + .name = "csi_v4l2", + }, + .probe = NULL, + .remove = NULL, +#ifdef CONFIG_PM + .suspend = csi_v4l2_suspend, + .resume = csi_v4l2_resume, +#endif + .shutdown = NULL, +}; + +/*! + * Initializes the camera driver. + */ +static int csi_v4l2_master_attach(struct v4l2_int_device *slave) +{ + cam_data *cam = slave->u.slave->master->priv; + struct v4l2_format cam_fmt; + + pr_debug("In MVC: %s\n", __func__); + pr_debug(" slave.name = %s\n", slave->name); + pr_debug(" master.name = %s\n", slave->u.slave->master->name); + + cam->sensor = slave; + if (slave == NULL) { + pr_err("ERROR: v4l2 capture: slave parameter not valid.\n"); + return -1; + } + + csi_enable_mclk(CSI_MCLK_I2C, true, true); + vidioc_int_dev_init(slave); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* Used to detect TV in (type 1) vs. camera (type 0) */ + cam->device_type = cam_fmt.fmt.pix.priv; + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + + return 0; +} + +/*! + * Disconnects the camera driver. + */ +static void csi_v4l2_master_detach(struct v4l2_int_device *slave) +{ + pr_debug("In MVC: %s\n", __func__); + + vidioc_int_dev_exit(slave); +} + +/*! + * Entry point for the V4L2 + * + * @return Error code indicating success or failure + */ +static __init int camera_init(void) +{ + u8 err = 0; + + /* Register the device driver structure. */ + err = platform_driver_register(&csi_v4l2_driver); + if (err != 0) { + pr_err("ERROR: v4l2 capture:camera_init: " + "platform_driver_register failed.\n"); + return err; + } + + /* Create g_cam and initialize it. */ + g_cam = kmalloc(sizeof(cam_data), GFP_KERNEL); + if (g_cam == NULL) { + pr_err("ERROR: v4l2 capture: failed to register camera\n"); + platform_driver_unregister(&csi_v4l2_driver); + return -1; + } + init_camera_struct(g_cam); + + /* Set up the v4l2 device and register it */ + csi_v4l2_int_device.priv = g_cam; + /* This function contains a bug that won't let this be rmmod'd. */ + v4l2_int_device_register(&csi_v4l2_int_device); + + /* Register the platform device */ + err = platform_device_register(&csi_v4l2_devices); + if (err != 0) { + pr_err("ERROR: v4l2 capture: camera_init: " + "platform_device_register failed.\n"); + platform_driver_unregister(&csi_v4l2_driver); + kfree(g_cam); + g_cam = NULL; + return err; + } + + /* register v4l video device */ + if (video_register_device(g_cam->video_dev, VFL_TYPE_GRABBER, video_nr) + == -1) { + platform_device_unregister(&csi_v4l2_devices); + platform_driver_unregister(&csi_v4l2_driver); + kfree(g_cam); + g_cam = NULL; + pr_err("ERROR: v4l2 capture: video_register_device failed\n"); + return -1; + } + pr_debug(" Video device registered: %s #%d\n", + g_cam->video_dev->name, g_cam->video_dev->minor); + + return err; +} + +/*! + * Exit and cleanup for the V4L2 + */ +static void __exit camera_exit(void) +{ + pr_debug("In MVC: %s\n", __func__); + + if (g_cam->open_count) { + pr_err("ERROR: v4l2 capture:camera open " + "-- setting ops to NULL\n"); + } else { + pr_info("V4L2 freeing image input device\n"); + v4l2_int_device_unregister(&csi_v4l2_int_device); + csi_stop_callback(g_cam); + video_unregister_device(g_cam->video_dev); + platform_driver_unregister(&csi_v4l2_driver); + platform_device_unregister(&csi_v4l2_devices); + + kfree(g_cam); + g_cam = NULL; + } +} + +module_init(camera_init); +module_exit(camera_exit); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2 capture driver for Mx25 based cameras"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/capture/emma_mt9v111.c b/drivers/media/video/mxc/capture/emma_mt9v111.c new file mode 100644 index 000000000000..73e9bba36d1e --- /dev/null +++ b/drivers/media/video/mxc/capture/emma_mt9v111.c @@ -0,0 +1,679 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mt9v111.c + * + * @brief mt9v111 camera driver functions + * + * @ingroup Camera + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include "mxc_v4l2_capture.h" +#include "mt9v111.h" + +#ifdef MT9V111_DEBUG +static u16 testpattern; +#endif + +static sensor_interface *interface_param; +static mt9v111_conf mt9v111_device; +static int reset_frame_rate = 30; + +#define MT9V111_FRAME_RATE_NUM 20 + +static mt9v111_image_format format[2] = { + { + .index = 0, + .width = 640, + .height = 480, + }, + { + .index = 1, + .width = 352, + .height = 288, + }, +}; + +static int mt9v111_attach(struct i2c_adapter *adapter); +static int mt9v111_detach(struct i2c_client *client); + +static struct i2c_driver mt9v111_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "MT9V111 Client", + }, + .attach_adapter = mt9v111_attach, + .detach_client = mt9v111_detach, +}; + +static struct i2c_client mt9v111_i2c_client = { + .name = "mt9v111 I2C dev", + .addr = MT9V111_I2C_ADDRESS, + .driver = &mt9v111_i2c_driver, +}; + +/* + * Function definitions + */ + +#ifdef MT9V111_DEBUG +static inline int mt9v111_read_reg(u8 reg) +{ + int val = i2c_smbus_read_word_data(&mt9v111_i2c_client, reg); + if (val != -1) + val = cpu_to_be16(val); + return val; +} +#endif + +static inline int mt9v111_write_reg(u8 reg, u16 val) +{ + pr_debug("write reg %x val %x.\n", reg, val); + return i2c_smbus_write_word_data(&mt9v111_i2c_client, reg, + cpu_to_be16(val)); +} + +/*! + * Initialize mt9v111_sensor_lib + * Libarary for Sensor configuration through I2C + * + * @param coreReg Core Registers + * @param ifpReg IFP Register + * + * @return status + */ +static u8 mt9v111_sensor_lib(mt9v111_coreReg * coreReg, mt9v111_IFPReg * ifpReg) +{ + u8 reg; + u16 data; + u8 error = 0; + + /* + * setup to IFP registers + */ + reg = MT9V111I_ADDR_SPACE_SEL; + data = ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + /* Operation Mode Control */ + reg = MT9V111I_MODE_CONTROL; + data = ifpReg->modeControl; + mt9v111_write_reg(reg, data); + + /* Output format */ + reg = MT9V111I_FORMAT_CONTROL; + data = ifpReg->formatControl; /* Set bit 12 */ + mt9v111_write_reg(reg, data); + + /* AE limit 4 */ + reg = MT9V111I_SHUTTER_WIDTH_LIMIT_AE; + data = ifpReg->gainLimitAE; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_OUTPUT_FORMAT_CTRL2; + data = ifpReg->outputFormatCtrl2; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_AE_SPEED; + data = ifpReg->AESpeed; + mt9v111_write_reg(reg, data); + + /* output image size */ + reg = MT9V111i_H_PAN; + data = 0x8000 | ifpReg->HPan; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_ZOOM; + data = 0x8000 | ifpReg->HZoom; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_SIZE; + data = 0x8000 | ifpReg->HSize; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_PAN; + data = 0x8000 | ifpReg->VPan; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_ZOOM; + data = 0x8000 | ifpReg->VZoom; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_SIZE; + data = 0x8000 | ifpReg->VSize; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_PAN; + data = ~0x8000 & ifpReg->HPan; + mt9v111_write_reg(reg, data); +#if 0 + reg = MT9V111I_UPPER_SHUTTER_DELAY_LIM; + data = ifpReg->upperShutterDelayLi; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_SHUTTER_60; + data = ifpReg->shutter_width_60; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_SEARCH_FLICK_60; + data = ifpReg->search_flicker_60; + mt9v111_write_reg(reg, data); +#endif + + /* + * setup to sensor core registers + */ + reg = MT9V111I_ADDR_SPACE_SEL; + data = coreReg->addressSelect; + mt9v111_write_reg(reg, data); + + /* enable changes and put the Sync bit on */ + reg = MT9V111S_OUTPUT_CTRL; + data = MT9V111S_OUTCTRL_SYNC | MT9V111S_OUTCTRL_CHIP_ENABLE | 0x3000; + mt9v111_write_reg(reg, data); + + /* min PIXCLK - Default */ + reg = MT9V111S_PIXEL_CLOCK_SPEED; + data = coreReg->pixelClockSpeed; + mt9v111_write_reg(reg, data); + + /* Setup image flipping / Dark rows / row/column skip */ + reg = MT9V111S_READ_MODE; + data = coreReg->readMode; + mt9v111_write_reg(reg, data); + + /*zoom 0 */ + reg = MT9V111S_DIGITAL_ZOOM; + data = coreReg->digitalZoom; + mt9v111_write_reg(reg, data); + + /* min H-blank */ + reg = MT9V111S_HOR_BLANKING; + data = coreReg->horizontalBlanking; + mt9v111_write_reg(reg, data); + + /* min V-blank */ + reg = MT9V111S_VER_BLANKING; + data = coreReg->verticalBlanking; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_SHUTTER_WIDTH; + data = coreReg->shutterWidth; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_SHUTTER_DELAY; + data = ifpReg->upperShutterDelayLi; + mt9v111_write_reg(reg, data); + + /* changes become effective */ + reg = MT9V111S_OUTPUT_CTRL; + data = MT9V111S_OUTCTRL_CHIP_ENABLE | 0x3000; + mt9v111_write_reg(reg, data); + + return error; +} + +/*! + * mt9v111 sensor interface Initialization + * @param param sensor_interface * + * @param width u32 + * @param height u32 + * @return None + */ +static void mt9v111_interface(sensor_interface *param, u32 width, u32 height) +{ + param->Vsync_pol = 0x0; + param->clk_mode = 0x0; /*gated */ + param->pixclk_pol = 0x0; + param->data_width = 0x1; + param->data_pol = 0x0; + param->ext_vsync = 0x0; + param->Vsync_pol = 0x0; + param->Hsync_pol = 0x0; + param->width = width - 1; + param->height = height - 1; + param->active_width = width; + param->active_height = height; + param->pixel_fmt = IPU_PIX_FMT_UYVY; + param->mclk = 27000000; +} + +/*! + * MT9V111 frame rate calculate + * + * @param frame_rate int * + * @param mclk int + * @return None + */ +static void mt9v111_rate_cal(int *frame_rate, int mclk) +{ + int num_clock_per_row; + int max_rate = 0; + + mt9v111_device.coreReg->horizontalBlanking = MT9V111_HORZBLANK_MIN; + + num_clock_per_row = (format[0].width + 114 + MT9V111_HORZBLANK_MIN) * 2; + max_rate = mclk / (num_clock_per_row * + (format[0].height + MT9V111_VERTBLANK_DEFAULT)); + + if ((*frame_rate > max_rate) || (*frame_rate == 0)) { + *frame_rate = max_rate; + } + + mt9v111_device.coreReg->verticalBlanking + = mclk / (*frame_rate * num_clock_per_row) - format[0].height; + + reset_frame_rate = *frame_rate; +} + +/*! + * MT9V111 sensor configuration + * + * @param frame_rate int * + * @param high_quality int + * @return sensor_interface * + */ +sensor_interface *mt9v111_config(int *frame_rate, int high_quality) +{ + u32 out_width, out_height; + + if (interface_param == NULL) + return NULL; + + mt9v111_device.coreReg->addressSelect = MT9V111I_SEL_SCA; + mt9v111_device.ifpReg->addrSpaceSel = MT9V111I_SEL_IFP; + + mt9v111_device.coreReg->windowHeight = MT9V111_WINHEIGHT; + mt9v111_device.coreReg->windowWidth = MT9V111_WINWIDTH; + mt9v111_device.coreReg->zoomColStart = 0; + mt9v111_device.coreReg->zomRowStart = 0; + mt9v111_device.coreReg->digitalZoom = 0x0; + + mt9v111_device.coreReg->verticalBlanking = MT9V111_VERTBLANK_DEFAULT; + mt9v111_device.coreReg->horizontalBlanking = MT9V111_HORZBLANK_MIN; + mt9v111_device.coreReg->pixelClockSpeed = 0; + mt9v111_device.coreReg->readMode = 0xd0a1; + + mt9v111_device.ifpReg->outputFormatCtrl2 = 0; + mt9v111_device.ifpReg->gainLimitAE = 0x300; + mt9v111_device.ifpReg->AESpeed = 0x80; + + /* here is the default value */ + mt9v111_device.ifpReg->formatControl = 0xc800; + mt9v111_device.ifpReg->modeControl = 0x708e; + mt9v111_device.ifpReg->awbSpeed = 0x4514; + mt9v111_device.coreReg->shutterWidth = 0xf8; + + out_width = 640; + out_height = 480; + + /*output size */ + mt9v111_device.ifpReg->HPan = 0; + mt9v111_device.ifpReg->HZoom = 640; + mt9v111_device.ifpReg->HSize = out_width; + mt9v111_device.ifpReg->VPan = 0; + mt9v111_device.ifpReg->VZoom = 480; + mt9v111_device.ifpReg->VSize = out_height; + + mt9v111_interface(interface_param, out_width, out_height); + set_mclk_rate(&interface_param->mclk); + mt9v111_rate_cal(frame_rate, interface_param->mclk); + mt9v111_sensor_lib(mt9v111_device.coreReg, mt9v111_device.ifpReg); + + return interface_param; +} + +/*! + * mt9v111 sensor set color configuration + * + * @param bright int + * @param saturation int + * @param red int + * @param green int + * @param blue int + * @return None + */ +static void +mt9v111_set_color(int bright, int saturation, int red, int green, int blue) +{ + u8 reg; + u16 data; + + switch (saturation) { + case 100: + mt9v111_device.ifpReg->awbSpeed = 0x4514; + break; + case 150: + mt9v111_device.ifpReg->awbSpeed = 0x6D14; + break; + case 75: + mt9v111_device.ifpReg->awbSpeed = 0x4D14; + break; + case 50: + mt9v111_device.ifpReg->awbSpeed = 0x5514; + break; + case 37: + mt9v111_device.ifpReg->awbSpeed = 0x5D14; + break; + case 25: + mt9v111_device.ifpReg->awbSpeed = 0x6514; + break; + default: + mt9v111_device.ifpReg->awbSpeed = 0x4514; + break; + } + + reg = MT9V111I_ADDR_SPACE_SEL; + data = mt9v111_device.ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + /* Operation Mode Control */ + reg = MT9V111I_AWB_SPEED; + data = mt9v111_device.ifpReg->awbSpeed; + mt9v111_write_reg(reg, data); +} + +/*! + * mt9v111 sensor get color configuration + * + * @param bright int * + * @param saturation int * + * @param red int * + * @param green int * + * @param blue int * + * @return None + */ +static void +mt9v111_get_color(int *bright, int *saturation, int *red, int *green, int *blue) +{ + *saturation = (mt9v111_device.ifpReg->awbSpeed & 0x3800) >> 11; + switch (*saturation) { + case 0: + *saturation = 100; + break; + case 1: + *saturation = 75; + break; + case 2: + *saturation = 50; + break; + case 3: + *saturation = 37; + break; + case 4: + *saturation = 25; + break; + case 5: + *saturation = 150; + break; + case 6: + *saturation = 0; + break; + default: + *saturation = 0; + break; + } +} + +/*! + * mt9v111 sensor set AE measurement window mode configuration + * + * @param ae_mode int + * @return None + */ +static void mt9v111_set_ae_mode(int ae_mode) +{ + u8 reg; + u16 data; + + mt9v111_device.ifpReg->modeControl &= 0xfff3; + mt9v111_device.ifpReg->modeControl |= (ae_mode & 0x03) << 2; + + reg = MT9V111I_ADDR_SPACE_SEL; + data = mt9v111_device.ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_MODE_CONTROL; + data = mt9v111_device.ifpReg->modeControl; + mt9v111_write_reg(reg, data); +} + +/*! + * mt9v111 sensor get AE measurement window mode configuration + * + * @param ae_mode int * + * @return None + */ +static void mt9v111_get_ae_mode(int *ae_mode) +{ + if (ae_mode != NULL) { + *ae_mode = (mt9v111_device.ifpReg->modeControl & 0xc) >> 2; + } +} + +/*! + * mt9v111 Reset function + * + * @return None + */ +static sensor_interface *mt9v111_reset(void) +{ + return mt9v111_config(&reset_frame_rate, 0); +} + +struct camera_sensor camera_sensor_if = { + .set_color = mt9v111_set_color, + .get_color = mt9v111_get_color, + .set_ae_mode = mt9v111_set_ae_mode, + .get_ae_mode = mt9v111_get_ae_mode, + .config = mt9v111_config, + .reset = mt9v111_reset, +}; + +#ifdef MT9V111_DEBUG +/*! + * Set sensor to test mode, which will generate test pattern. + * + * @return none + */ +static void mt9v111_test_pattern(bool flag) +{ + u16 data; + + /* switch to sensor registers */ + mt9v111_write_reg(MT9V111I_ADDR_SPACE_SEL, MT9V111I_SEL_SCA); + + if (flag == true) { + testpattern = MT9V111S_OUTCTRL_TEST_MODE; + + data = mt9v111_read_reg(MT9V111S_ROW_NOISE_CTRL) & 0xBF; + mt9v111_write_reg(MT9V111S_ROW_NOISE_CTRL, data); + + mt9v111_write_reg(MT9V111S_TEST_DATA, 0); + + /* changes take effect */ + data = MT9V111S_OUTCTRL_CHIP_ENABLE | testpattern | 0x3000; + mt9v111_write_reg(MT9V111S_OUTPUT_CTRL, data); + } else { + testpattern = 0; + + data = mt9v111_read_reg(MT9V111S_ROW_NOISE_CTRL) | 0x40; + mt9v111_write_reg(MT9V111S_ROW_NOISE_CTRL, data); + + /* changes take effect */ + data = MT9V111S_OUTCTRL_CHIP_ENABLE | testpattern | 0x3000; + mt9v111_write_reg(MT9V111S_OUTPUT_CTRL, data); + } +} +#endif + +/*! + * mt9v111 I2C detect_client function + * + * @param adapter struct i2c_adapter * + * @param address int + * @param kind int + * + * @return Error code indicating success or failure + */ +static int mt9v111_detect_client(struct i2c_adapter *adapter, int address, + int kind) +{ + mt9v111_i2c_client.adapter = adapter; + if (i2c_attach_client(&mt9v111_i2c_client)) { + mt9v111_i2c_client.adapter = NULL; + printk(KERN_ERR "mt9v111_attach: i2c_attach_client failed\n"); + return -1; + } + + interface_param = (sensor_interface *) + kmalloc(sizeof(sensor_interface), GFP_KERNEL); + if (!interface_param) { + printk(KERN_ERR "mt9v111_attach: kmalloc failed \n"); + return -1; + } + + printk(KERN_INFO "MT9V111 Detected\n"); + + return 0; +} + +static unsigned short normal_i2c[] = { MT9V111_I2C_ADDRESS, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +/*! + * mt9v111 I2C attach function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int mt9v111_attach(struct i2c_adapter *adap) +{ + uint32_t mclk = 27000000; + struct clk *clk; + int err; + + clk = clk_get(NULL, "csi_clk"); + clk_enable(clk); + set_mclk_rate(&mclk); + + err = i2c_probe(adap, &addr_data, &mt9v111_detect_client); + + clk_disable(clk); + clk_put(clk); + + return err; +} + +/*! + * mt9v111 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int mt9v111_detach(struct i2c_client *client) +{ + int err; + + if (!mt9v111_i2c_client.adapter) + return -1; + + err = i2c_detach_client(&mt9v111_i2c_client); + mt9v111_i2c_client.adapter = NULL; + + if (interface_param) + kfree(interface_param); + interface_param = NULL; + + return err; +} + +extern void gpio_sensor_active(void); + +/*! + * MT9V111 init function + * + * @return Error code indicating success or failure + */ +static __init int mt9v111_init(void) +{ + u8 err; + + gpio_sensor_active(); + + mt9v111_device.coreReg = (mt9v111_coreReg *) + kmalloc(sizeof(mt9v111_coreReg), GFP_KERNEL); + if (!mt9v111_device.coreReg) + return -1; + + memset(mt9v111_device.coreReg, 0, sizeof(mt9v111_coreReg)); + + mt9v111_device.ifpReg = (mt9v111_IFPReg *) + kmalloc(sizeof(mt9v111_IFPReg), GFP_KERNEL); + if (!mt9v111_device.ifpReg) { + kfree(mt9v111_device.coreReg); + mt9v111_device.coreReg = NULL; + return -1; + } + + memset(mt9v111_device.ifpReg, 0, sizeof(mt9v111_IFPReg)); + + err = i2c_add_driver(&mt9v111_i2c_driver); + + return err; +} + +extern void gpio_sensor_inactive(void); +/*! + * MT9V111 cleanup function + * + * @return Error code indicating success or failure + */ +static void __exit mt9v111_clean(void) +{ + if (mt9v111_device.coreReg) { + kfree(mt9v111_device.coreReg); + mt9v111_device.coreReg = NULL; + } + + if (mt9v111_device.ifpReg) { + kfree(mt9v111_device.ifpReg); + mt9v111_device.ifpReg = NULL; + } + + i2c_del_driver(&mt9v111_i2c_driver); + + gpio_sensor_inactive(); +} + +module_init(mt9v111_init); +module_exit(mt9v111_clean); + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(camera_sensor_if); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Mt9v111 Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/emma_ov2640.c b/drivers/media/video/mxc/capture/emma_ov2640.c new file mode 100644 index 000000000000..ceffea4d52a9 --- /dev/null +++ b/drivers/media/video/mxc/capture/emma_ov2640.c @@ -0,0 +1,444 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> + +#include "mxc_v4l2_capture.h" + +enum ov2640_mode { + ov2640_mode_1600_1120, + ov2640_mode_800_600 +}; + +struct reg_value { + u8 reg; + u8 value; + int delay_ms; +}; + +static struct reg_value ov2640_setting_1600_1120[] = { + {0xff, 0x1, 0}, {0x12, 0x80, 1}, {0xff, 0, 0}, {0x2c, 0xff, 0}, + {0x2e, 0xdf, 0}, {0xff, 0x1, 0}, {0x3c, 0x32, 0}, {0x11, 0x01, 0}, + {0x09, 0x00, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, {0x14, 0x48, 0}, + {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, {0x3b, 0xfb, 0}, + {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, {0x39, 0x82, 0}, + {0x35, 0x88, 0}, {0x22, 0x0a, 0}, {0x37, 0x40, 0}, {0x23, 0x00, 0}, + {0x34, 0xa0, 0}, {0x36, 0x1a, 0}, {0x06, 0x02, 0}, {0x07, 0xc0, 0}, + {0x0d, 0xb7, 0}, {0x0e, 0x01, 0}, {0x4c, 0x00, 0}, {0x4a, 0x81, 0}, + {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, {0x26, 0x82, 0}, + {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x3f, 0}, {0x0c, 0x3c, 0}, + {0x5d, 0x55, 0}, {0x5e, 0x7d, 0}, {0x5f, 0x7d, 0}, {0x60, 0x55, 0}, + {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, {0x20, 0x80, 0}, + {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, {0x6e, 00, 0}, + {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, {0x3d, 0x34, 0}, + {0x5a, 0x57, 0}, {0x4f, 0xbb, 0}, {0x50, 0x9c, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0x44, 0x06, 0}, + {0xe0, 0x14, 0}, {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, + {0x43, 0x18, 0}, {0x4c, 0x00, 0}, {0x87, 0xd0, 0}, {0xd7, 0x03, 0}, + {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, {0xc9, 0x80, 0}, + {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, {0x7d, 0x48, 0}, + {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, {0x7d, 0x10, 0}, + {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, {0x91, 0x1a, 0}, + {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, {0x91, 0x75, 0}, + {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, {0x91, 0x96, 0}, + {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, {0x91, 0xd7, 0}, + {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, {0x93, 0x06, 0}, + {0x93, 0xe3, 0}, {0x93, 0x03, 0}, {0x93, 0x03, 0}, {0x93, 0x00, 0}, + {0x93, 0x02, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, {0x97, 0x02, 0}, + {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, {0x97, 0x28, 0}, + {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, {0x97, 0x80, 0}, + {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xa4, 0x00, 0}, {0xa8, 0x00, 0}, + {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, {0xc7, 0x10, 0}, + {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, {0xb9, 0x7c, 0}, + {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, {0xb0, 0xc5, 0}, + {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, {0xa6, 0x00, 0}, + {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x1b, 0}, {0xa7, 0x31, 0}, + {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, + {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, + {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, + {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xc0, 0xc8, 0}, {0xc1, 0x96, 0}, + {0x86, 0x3d, 0}, {0x50, 0x00, 0}, {0x51, 0x90, 0}, {0x52, 0x18, 0}, + {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x88, 0}, {0x57, 0x00, 0}, + {0x5a, 0x90, 0}, {0x5b, 0x18, 0}, {0x5c, 0x05, 0}, {0xc3, 0xef, 0}, + {0x7f, 0x00, 0}, {0xda, 0x01, 0}, {0xe5, 0x1f, 0}, {0xe1, 0x67, 0}, + {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0} +}; + +static struct reg_value ov2640_setting_800_600[] = { + {0xff, 0, 0}, {0xff, 1, 0}, {0x12, 0x80, 1}, {0xff, 00, 0}, + {0x2c, 0xff, 0}, {0x2e, 0xdf, 0}, {0xff, 0x1, 0}, {0x3c, 0x32, 0}, + {0x11, 0x01, 0}, {0x09, 0x00, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, + {0x14, 0x48, 0}, {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, + {0x3b, 0xfb, 0}, {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, + {0x39, 0x92, 0}, {0x35, 0xda, 0}, {0x22, 0x1a, 0}, {0x37, 0xc3, 0}, + {0x23, 0x00, 0}, {0x34, 0xc0, 0}, {0x36, 0x1a, 0}, {0x06, 0x88, 0}, + {0x07, 0xc0, 0}, {0x0d, 0x87, 0}, {0x0e, 0x41, 0}, {0x4c, 0x00, 0}, + {0x4a, 0x81, 0}, {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, + {0x26, 0x82, 0}, {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x22, 0}, + {0x0c, 0x3c, 0}, {0x5d, 0x55, 0}, {0x5e, 0x7d, 0}, {0x5f, 0x7d, 0}, + {0x60, 0x55, 0}, {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, + {0x20, 0x80, 0}, {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, + {0x6e, 00, 0}, {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, + {0x12, 0x40, 0}, {0x17, 0x11, 0}, {0x18, 0x43, 0}, {0x19, 0x00, 0}, + {0x1a, 0x4b, 0}, {0x32, 0x09, 0}, {0x37, 0xc0, 0}, {0x4f, 0xca, 0}, + {0x50, 0xa8, 0}, {0x6d, 0x00, 0}, {0x3d, 0x38, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0x44, 0x06, 0}, + {0xe0, 0x14, 0}, {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, + {0x43, 0x18, 0}, {0x4c, 0x00, 0}, {0x87, 0xd0, 0}, {0x88, 0x3f, 0}, + {0xd7, 0x03, 0}, {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, + {0xc9, 0x80, 0}, {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, + {0x7d, 0x48, 0}, {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, + {0x7d, 0x10, 0}, {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, + {0x91, 0x1a, 0}, {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, + {0x91, 0x75, 0}, {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, + {0x91, 0x96, 0}, {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, + {0x91, 0xd7, 0}, {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, + {0x93, 0x06, 0}, {0x93, 0xe3, 0}, {0x93, 0x03, 0}, {0x93, 0x03, 0}, + {0x93, 0x00, 0}, {0x93, 0x02, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, + {0x97, 0x02, 0}, {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, + {0x97, 0x28, 0}, {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, + {0x97, 0x80, 0}, {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xa4, 0x00, 0}, + {0xa8, 0x00, 0}, {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, + {0xc7, 0x10, 0}, {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, + {0xb9, 0x7c, 0}, {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, + {0xb0, 0xc5, 0}, {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, + {0xa6, 0x00, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x1b, 0}, + {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, + {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, + {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, + {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xc0, 0x64, 0}, + {0xc1, 0x4b, 0}, {0x86, 0x1d, 0}, {0x50, 0x00, 0}, {0x51, 0xc8, 0}, + {0x52, 0x96, 0}, {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x00, 0}, + {0x57, 0x00, 0}, {0x5a, 0xc8, 0}, {0x5b, 0x96, 0}, {0x5c, 0x00, 0}, + {0xc3, 0xef, 0}, {0x7f, 0x00, 0}, {0xda, 0x01, 0}, {0xe5, 0x1f, 0}, + {0xe1, 0x67, 0}, {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0} +}; + +static struct regulator *io_regulator; +static struct regulator *core_regulator; +static struct regulator *analog_regulator; +static struct regulator *gpo_regulator; +u32 mclk = 24000000; + +struct i2c_client *ov2640_i2c_client; + +static sensor_interface *interface_param; +static int reset_frame_rate = 30; +static int ov2640_probe(struct i2c_client *adapter, + const struct i2c_device_id *id); +static int ov2640_remove(struct i2c_client *client); + +static const struct i2c_device_id ov2640_id[] = { + {"ov2640", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov2640_id); + +static struct i2c_driver ov2640_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ov2640", + }, + .probe = ov2640_probe, + .remove = ov2640_remove, + .id_table = ov2640_id, +}; + +/*! + * ov2640 I2C attach function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int ov2640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mxc_camera_platform_data *plat_data = client->dev.platform_data; + + ov2640_i2c_client = client; + mclk = plat_data->mclk; + + io_regulator = regulator_get(&client->dev, plat_data->io_regulator); + core_regulator = regulator_get(&client->dev, plat_data->core_regulator); + analog_regulator = + regulator_get(&client->dev, plat_data->analog_regulator); + gpo_regulator = regulator_get(&client->dev, plat_data->gpo_regulator); + + interface_param = (sensor_interface *) + kmalloc(sizeof(sensor_interface), GFP_KERNEL); + if (!interface_param) { + dev_dbg(&ov2640_i2c_client->dev, + "ov2640_probe: kmalloc failed \n"); + return -1; + } + + return 0; +} + +/*! + * ov2640 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int ov2640_remove(struct i2c_client *client) +{ + kfree(interface_param); + interface_param = NULL; + + if (!IS_ERR_VALUE((unsigned long)io_regulator)) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } + + if (!IS_ERR_VALUE((unsigned long)core_regulator)) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } + + if (!IS_ERR_VALUE((unsigned long)gpo_regulator)) { + regulator_disable(gpo_regulator); + regulator_put(gpo_regulator); + } + + if (!IS_ERR_VALUE((unsigned long)analog_regulator)) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } + + return 0; +} + +static int ov2640_write_reg(u8 reg, u8 val) +{ + if (i2c_smbus_write_byte_data(ov2640_i2c_client, reg, val) < 0) { + dev_dbg(&ov2640_i2c_client->dev, + "%s:write reg errorr:reg=%x,val=%x\n", __func__, reg, + val); + return -1; + } + return 0; +} + +static int ov2640_init_mode(enum ov2640_mode mode) +{ + struct reg_value *setting; + int i, num; + + switch (mode) { + case ov2640_mode_1600_1120: + setting = ov2640_setting_1600_1120; + num = ARRAY_SIZE(ov2640_setting_1600_1120); + break; + case ov2640_mode_800_600: + setting = ov2640_setting_800_600; + num = ARRAY_SIZE(ov2640_setting_800_600); + break; + default: + return 0; + } + + for (i = 0; i < num; i++) { + ov2640_write_reg(setting[i].reg, setting[i].value); + if (setting[i].delay_ms > 0) + msleep(setting[i].delay_ms); + } + + return 0; +} + +/*! + * ov2640 sensor interface Initialization + * @param param sensor_interface * + * @param width u32 + * @param height u32 + * @return None + */ +static void ov2640_interface(sensor_interface *param, u32 width, u32 height) +{ + param->Vsync_pol = 0x0; + param->clk_mode = 0x0; /*gated */ + param->pixclk_pol = 0x0; + param->data_width = 0x1; + param->data_pol = 0x0; + param->ext_vsync = 0x0; + param->Vsync_pol = 0x0; + param->Hsync_pol = 0x0; + param->width = width - 1; + param->height = height - 1; + param->active_width = width; + param->active_height = height; + param->pixel_fmt = IPU_PIX_FMT_UYVY; + param->mclk = mclk; +} + +static void ov2640_set_color(int bright, int saturation, int red, int green, + int blue) +{ + +} + +static void ov2640_get_color(int *bright, int *saturation, int *red, int *green, + int *blue) +{ + +} +static void ov2640_set_ae_mode(int ae_mode) +{ + +} +static void ov2640_get_ae_mode(int *ae_mode) +{ + +} + +extern void gpio_sensor_active(void); + +static sensor_interface *ov2640_config(int *frame_rate, int high_quality) +{ + + u32 out_width, out_height; + + /*set io votage */ + if (!IS_ERR_VALUE((unsigned long)io_regulator)) { + regulator_set_voltage(io_regulator, 2800000, 2800000); + if (regulator_enable(io_regulator) != 0) { + dev_dbg(&ov2640_i2c_client->dev, + "%s:io set voltage error\n", __func__); + return NULL; + } else { + dev_dbg(&ov2640_i2c_client->dev, + "%s:io set voltage ok\n", __func__); + } + } + + /*core votage */ + if (!IS_ERR_VALUE((unsigned long)core_regulator)) { + regulator_set_voltage(core_regulator, 1300000, 1300000); + if (regulator_enable(core_regulator) != 0) { + dev_dbg(&ov2640_i2c_client->dev, + "%s:core set voltage error\n", __func__); + return NULL; + } else { + dev_dbg(&ov2640_i2c_client->dev, + "%s:core set voltage ok\n", __func__); + } + } + + /*GPO 3 */ + if (!IS_ERR_VALUE((unsigned long)gpo_regulator)) { + if (regulator_enable(gpo_regulator) != 0) { + dev_dbg(&ov2640_i2c_client->dev, + "%s:gpo3 enable error\n", __func__); + return NULL; + } else { + dev_dbg(&ov2640_i2c_client->dev, "%s:gpo3 enable ok\n", + __func__); + } + } + + if (!IS_ERR_VALUE((unsigned long)analog_regulator)) { + regulator_set_voltage(analog_regulator, 2000000, 2000000); + if (regulator_enable(analog_regulator) != 0) { + dev_dbg(&ov2640_i2c_client->dev, + "%s:analog set voltage error\n", __func__); + return NULL; + } else { + dev_dbg(&ov2640_i2c_client->dev, + "%s:analog set voltage ok\n", __func__); + } + } + + gpio_sensor_active(); + + if (high_quality) { + out_width = 1600; + out_height = 1120; + } else { + out_width = 800; + out_height = 600; + } + ov2640_interface(interface_param, out_width, out_height); + set_mclk_rate(&interface_param->mclk); + + if (high_quality) + ov2640_init_mode(ov2640_mode_1600_1120); + else + ov2640_init_mode(ov2640_mode_800_600); + + msleep(300); + + return interface_param; +} + +static sensor_interface *ov2640_reset(void) +{ + return ov2640_config(&reset_frame_rate, 0); +} + +struct camera_sensor camera_sensor_if = { + .set_color = ov2640_set_color, + .get_color = ov2640_get_color, + .set_ae_mode = ov2640_set_ae_mode, + .get_ae_mode = ov2640_get_ae_mode, + .config = ov2640_config, + .reset = ov2640_reset, +}; + +EXPORT_SYMBOL(camera_sensor_if); + +/*! + * ov2640 init function + * + * @return Error code indicating success or failure + */ +static __init int ov2640_init(void) +{ + u8 err; + + err = i2c_add_driver(&ov2640_i2c_driver); + + return err; +} + +extern void gpio_sensor_inactive(void); +/*! + * OV2640 cleanup function + * + * @return Error code indicating success or failure + */ +static void __exit ov2640_clean(void) +{ + i2c_del_driver(&ov2640_i2c_driver); + + gpio_sensor_inactive(); +} + +module_init(ov2640_init); +module_exit(ov2640_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OV2640 Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/emma_v4l2_capture.c b/drivers/media/video/mxc/capture/emma_v4l2_capture.c new file mode 100644 index 000000000000..170807716ec6 --- /dev/null +++ b/drivers/media/video/mxc/capture/emma_v4l2_capture.c @@ -0,0 +1,2075 @@ +/* + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_v4l2_capture.c + * + * @brief MX27 Video For Linux 2 driver + * + * @ingroup MXC_V4L2_CAPTURE + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/types.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/semaphore.h> +#include <linux/version.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> + +#include "mxc_v4l2_capture.h" +#include "mx27_prp.h" +#include "mx27_csi.h" + +static int csi_mclk_flag_backup; +static int video_nr = -1; +static cam_data *g_cam; + +/*! + * Free frame buffers + * + * @param cam Structure cam_data * + * + * @return status 0 success. + */ +static int mxc_free_frame_buf(cam_data *cam) +{ + int i; + + for (i = 0; i < FRAME_NUM; i++) { + if (cam->frame[i].vaddress != 0) { + dma_free_coherent(0, + cam->frame[i].buffer.length, + cam->frame[i].vaddress, + cam->frame[i].paddress); + cam->frame[i].vaddress = 0; + } + } + + return 0; +} + +/*! + * Allocate frame buffers + * + * @param cam Structure cam_data * + * + * @param count int number of buffer need to allocated + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_frame_buf(cam_data *cam, int count) +{ + int i; + + for (i = 0; i < count; i++) { + cam->frame[i].vaddress = + dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f. fmt.pix.sizeimage), + &cam->frame[i].paddress, + GFP_DMA | GFP_KERNEL); + if (cam->frame[i].vaddress == 0) { + pr_debug("mxc_allocate_frame_buf failed.\n"); + mxc_free_frame_buf(cam); + return -ENOBUFS; + } + cam->frame[i].buffer.index = i; + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + cam->frame[i].buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->frame[i].buffer.length = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->frame[i].buffer.memory = V4L2_MEMORY_MMAP; + cam->frame[i].buffer.m.offset = cam->frame[i].paddress; + cam->frame[i].index = i; + } + + return 0; +} + +/*! + * Free frame buffers status + * + * @param cam Structure cam_data * + * + * @return none + */ +static void mxc_free_frames(cam_data *cam) +{ + int i; + + for (i = 0; i < FRAME_NUM; i++) { + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + } + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); +} + +/*! + * Return the buffer status + * + * @param cam Structure cam_data * + * @param buf Structure v4l2_buffer * + * + * @return status 0 success, EINVAL failed. + */ +static int mxc_v4l2_buffer_status(cam_data *cam, struct v4l2_buffer *buf) +{ + /* check range */ + if (buf->index < 0 || buf->index >= FRAME_NUM) { + pr_debug("mxc_v4l2_buffer_status buffers not allocated\n"); + return -EINVAL; + } + + memcpy(buf, &(cam->frame[buf->index].buffer), sizeof(*buf)); + return 0; +} + +/*! + * start the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamon(cam_data *cam) +{ + struct mxc_v4l_frame *frame; + int err = 0; + + if (!cam) + return -EIO; + + if (list_empty(&cam->ready_q)) { + printk(KERN_ERR "mxc_streamon buffer not been queued yet\n"); + return -EINVAL; + } + + cam->capture_pid = current->pid; + + if (cam->enc_enable) { + err = cam->enc_enable(cam); + if (err != 0) { + return err; + } + } + + cam->ping_pong_csi = 0; + if (cam->enc_update_eba) { + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + err = cam->enc_update_eba(frame->paddress, &cam->ping_pong_csi); + + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + err |= + cam->enc_update_eba(frame->paddress, &cam->ping_pong_csi); + } else { + return -EINVAL; + } + + return err; +} + +/*! + * Shut down the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamoff(cam_data *cam) +{ + int err = 0; + + if (!cam) + return -EIO; + + if (cam->enc_disable) { + err = cam->enc_disable(cam); + } + mxc_free_frames(cam); + return err; +} + +/*! + * Valid whether the palette is supported + * + * @param palette pixel format + * + * @return 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + /* + * MX27 PrP channel 2 supports YUV444, but YUV444 is not + * defined by V4L2 :( + */ + return ((palette == V4L2_PIX_FMT_YUYV) || + (palette == V4L2_PIX_FMT_YUV420)); +} + +/*! + * Valid and adjust the overlay window size, position + * + * @param cam structure cam_data * + * @param win struct v4l2_window * + * + * @return 0 + */ +static int verify_preview(cam_data *cam, struct v4l2_window *win) +{ + if (cam->output >= num_registered_fb) { + pr_debug("verify_preview No matched.\n"); + return -1; + } + cam->overlay_fb = (struct fb_info *)registered_fb[cam->output]; + + /* TODO: suppose 16bpp, 4 bytes alignment */ + win->w.left &= ~0x1; + + if (win->w.width + win->w.left > cam->overlay_fb->var.xres) + win->w.width = cam->overlay_fb->var.xres - win->w.left; + if (win->w.height + win->w.top > cam->overlay_fb->var.yres) + win->w.height = cam->overlay_fb->var.yres - win->w.top; + + /* + * TODO: suppose 16bpp. Rounded down to a multiple of 2 pixels for + * width according to PrP limitations. + */ + if ((cam->rotation == V4L2_MXC_ROTATE_90_RIGHT) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_VFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_HFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_LEFT)) + win->w.height &= ~0x1; + else + win->w.width &= ~0x1; + + return 0; +} + +/*! + * start the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int start_preview(cam_data *cam) +{ + int err = 0; + + err = prp_vf_select(cam); + if (err != 0) + return err; + + cam->overlay_pid = current->pid; + err = cam->vf_start_sdc(cam); + + return err; +} + +/*! + * shut down the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int stop_preview(cam_data *cam) +{ + int err = 0; + + err = prp_vf_deselect(cam); + return err; +} + +/*! + * V4L2 - mxc_v4l2_g_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + f->fmt.pix.width = cam->v2f.fmt.pix.width; + f->fmt.pix.height = cam->v2f.fmt.pix.height; + f->fmt.pix.sizeimage = cam->v2f.fmt.pix.sizeimage; + f->fmt.pix.pixelformat = cam->v2f.fmt.pix.pixelformat; + f->fmt.pix.bytesperline = cam->v2f.fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG; + retval = 0; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + f->fmt.win = cam->win; + break; + default: + retval = -EINVAL; + } + return retval; +} + +/*! + * V4L2 - mxc_v4l2_s_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + int size = 0; + int bytesperline = 0; + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_debug("mxc_v4l2_s_fmt: format not supported\n"); + retval = -EINVAL; + } + + if (cam->rotation != V4L2_MXC_ROTATE_NONE) + pr_debug("mxc_v4l2_s_fmt: capture rotation ignored\n"); + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUYV: + f->fmt.pix.width &= ~0x1; /* Multiple of 2 */ + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_YUV420: + f->fmt.pix.width &= ~0x7; /* Multiple of 8 */ + f->fmt.pix.height &= ~0x1; /* Multiple of 2 */ + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + bytesperline = f->fmt.pix.width * 3 / 2; + break; + default: + /* Suppose it's YUV444 or 32bpp */ + size = f->fmt.pix.width * f->fmt.pix.height * 4; + bytesperline = f->fmt.pix.width * 4; + pr_info("mxc_v4l2_s_fmt: default assume" + " to be YUV444 interleaved.\n"); + break; + } + + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + if (f->fmt.pix.sizeimage > size) { + pr_debug("mxc_v4l2_s_fmt: sizeimage bigger than" + " needed.\n"); + size = f->fmt.pix.sizeimage; + } + f->fmt.pix.sizeimage = size; + + cam->v2f.fmt.pix.sizeimage = size; + cam->v2f.fmt.pix.bytesperline = bytesperline; + cam->v2f.fmt.pix.width = f->fmt.pix.width; + cam->v2f.fmt.pix.height = f->fmt.pix.height; + cam->v2f.fmt.pix.pixelformat = f->fmt.pix.pixelformat; + retval = 0; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + retval = verify_preview(cam, &f->fmt.win); + cam->win = f->fmt.win; + break; + default: + retval = -EINVAL; + } + return retval; +} + +/*! + * get control param + * + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42l_control(cam_data *cam, struct v4l2_control *c) +{ + int status = 0; + + switch (c->id) { + case V4L2_CID_HFLIP: + c->value = cam->rotation; + break; + case V4L2_CID_VFLIP: + c->value = cam->rotation; + break; + case V4L2_CID_MXC_ROT: + c->value = cam->rotation; + break; + case V4L2_CID_BRIGHTNESS: + c->value = cam->bright; + break; + case V4L2_CID_HUE: + c->value = cam->hue; + break; + case V4L2_CID_CONTRAST: + c->value = cam->contrast; + break; + case V4L2_CID_SATURATION: + c->value = cam->saturation; + break; + case V4L2_CID_RED_BALANCE: + c->value = cam->red; + break; + case V4L2_CID_BLUE_BALANCE: + c->value = cam->blue; + break; + case V4L2_CID_BLACK_LEVEL: + c->value = cam->ae_mode; + break; + default: + status = -EINVAL; + } + return status; +} + +/*! + * V4L2 - set_control function + * V4L2_CID_MXC_ROT is the extention for rotation/mirroring. + * + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42l_control(cam_data *cam, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + if (c->value == 1) { + if ((cam->rotation != V4L2_MXC_ROTATE_VERT_FLIP) && + (cam->rotation != V4L2_MXC_ROTATE_180)) + cam->rotation = V4L2_MXC_ROTATE_HORIZ_FLIP; + else + cam->rotation = V4L2_MXC_ROTATE_180; + } else { + if (cam->rotation == V4L2_MXC_ROTATE_HORIZ_FLIP) + cam->rotation = V4L2_MXC_ROTATE_NONE; + else if (cam->rotation == V4L2_MXC_ROTATE_180) + cam->rotation = V4L2_MXC_ROTATE_VERT_FLIP; + } + break; + case V4L2_CID_VFLIP: + if (c->value == 1) { + if ((cam->rotation != V4L2_MXC_ROTATE_HORIZ_FLIP) && + (cam->rotation != V4L2_MXC_ROTATE_180)) + cam->rotation = V4L2_MXC_ROTATE_VERT_FLIP; + else + cam->rotation = V4L2_MXC_ROTATE_180; + } else { + if (cam->rotation == V4L2_MXC_ROTATE_VERT_FLIP) + cam->rotation = V4L2_MXC_ROTATE_NONE; + if (cam->rotation == V4L2_MXC_ROTATE_180) + cam->rotation = V4L2_MXC_ROTATE_HORIZ_FLIP; + } + break; + case V4L2_CID_MXC_ROT: + switch (c->value) { + case V4L2_MXC_ROTATE_NONE: + case V4L2_MXC_ROTATE_VERT_FLIP: + case V4L2_MXC_ROTATE_HORIZ_FLIP: + case V4L2_MXC_ROTATE_180: + case V4L2_MXC_ROTATE_90_RIGHT: + case V4L2_MXC_ROTATE_90_RIGHT_VFLIP: + case V4L2_MXC_ROTATE_90_RIGHT_HFLIP: + case V4L2_MXC_ROTATE_90_LEFT: + cam->rotation = c->value; + break; + default: + return -EINVAL; + } + break; + case V4L2_CID_HUE: + cam->hue = c->value; + break; + case V4L2_CID_CONTRAST: + cam->contrast = c->value; + break; + case V4L2_CID_BRIGHTNESS: + cam->bright = c->value; + case V4L2_CID_SATURATION: + cam->saturation = c->value; + case V4L2_CID_RED_BALANCE: + cam->red = c->value; + case V4L2_CID_BLUE_BALANCE: + cam->blue = c->value; + csi_enable_mclk(CSI_MCLK_I2C, true, true); + cam->cam_sensor->set_color(cam->bright, cam->saturation, + cam->red, cam->green, cam->blue); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + break; + case V4L2_CID_BLACK_LEVEL: + cam->ae_mode = c->value & 0x03; + csi_enable_mclk(CSI_MCLK_I2C, true, true); + if (cam->cam_sensor->set_ae_mode) + cam->cam_sensor->set_ae_mode(cam->ae_mode); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + break; + case V4L2_CID_MXC_FLASH: + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 - mxc_v4l2_s_param function + * + * @param cam structure cam_data * + * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_param(cam_data *cam, struct v4l2_streamparm *parm) +{ + sensor_interface *param; + csi_signal_cfg_t csi_param; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_debug("mxc_v4l2_s_param invalid type\n"); + return -EINVAL; + } + + if (parm->parm.capture.timeperframe.denominator > + cam->standard.frameperiod.denominator) { + pr_debug("mxc_v4l2_s_param frame rate %d larger " + "than standard supported %d\n", + parm->parm.capture.timeperframe.denominator, + cam->standard.frameperiod.denominator); + return -EINVAL; + } + + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + + csi_enable_mclk(CSI_MCLK_I2C, true, true); + param = cam->cam_sensor->config + (&parm->parm.capture.timeperframe.denominator, + parm->parm.capture.capturemode); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + + cam->streamparm.parm.capture.timeperframe = + parm->parm.capture.timeperframe; + + if ((parm->parm.capture.capturemode != 0) && + (parm->parm.capture.capturemode != V4L2_MODE_HIGHQUALITY)) { + pr_debug("mxc_v4l2_s_param frame un-supported capture mode\n"); + return -EINVAL; + } + + if (parm->parm.capture.capturemode == + cam->streamparm.parm.capture.capturemode) { + return 0; + } + + /* resolution changed, so need to re-program the CSI */ + csi_param.sens_clksrc = 0; + csi_param.clk_mode = param->clk_mode; + csi_param.pixclk_pol = param->pixclk_pol; + csi_param.data_width = param->data_width; + csi_param.data_pol = param->data_pol; + csi_param.ext_vsync = param->ext_vsync; + csi_param.Vsync_pol = param->Vsync_pol; + csi_param.Hsync_pol = param->Hsync_pol; + csi_init_interface(param->width, param->height, param->pixel_fmt, + csi_param); + + if (parm->parm.capture.capturemode != V4L2_MODE_HIGHQUALITY) { + cam->streamparm.parm.capture.capturemode = 0; + } else { + cam->streamparm.parm.capture.capturemode = + V4L2_MODE_HIGHQUALITY; + cam->streamparm.parm.capture.extendedmode = + parm->parm.capture.extendedmode; + cam->streamparm.parm.capture.readbuffers = 1; + } + return 0; +} + +/*! + * Dequeue one V4L capture buffer + * + * @param cam structure cam_data * + * @param buf structure v4l2_buffer * + * + * @return status 0 success, EINVAL invalid frame number, + * ETIME timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_dqueue(cam_data *cam, struct v4l2_buffer *buf) +{ + int retval = 0; + struct mxc_v4l_frame *frame; + + if (!wait_event_interruptible_timeout(cam->enc_queue, + cam->enc_counter != 0, 10 * HZ)) { + printk(KERN_ERR "mxc_v4l_dqueue timeout enc_counter %x\n", + cam->enc_counter); + return -ETIME; + } else if (signal_pending(current)) { + printk(KERN_ERR "mxc_v4l_dqueue() interrupt received\n"); + return -ERESTARTSYS; + } + + cam->enc_counter--; + + frame = list_entry(cam->done_q.next, struct mxc_v4l_frame, queue); + list_del(cam->done_q.next); + if (frame->buffer.flags & V4L2_BUF_FLAG_DONE) { + frame->buffer.flags &= ~V4L2_BUF_FLAG_DONE; + } else if (frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + printk(KERN_ERR "VIDIOC_DQBUF: Buffer not filled.\n"); + frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } else if ((frame->buffer.flags & 0x7) == V4L2_BUF_FLAG_MAPPED) { + printk(KERN_ERR "VIDIOC_DQBUF: Buffer not queued.\n"); + retval = -EINVAL; + } + + buf->bytesused = cam->v2f.fmt.pix.sizeimage; + buf->index = frame->index; + buf->flags = frame->buffer.flags; + + return retval; +} + +/*! + * V4L interface - open function + * + * @param inode structure inode * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_open(struct inode *inode, struct file *file) +{ + sensor_interface *param; + csi_signal_cfg_t csi_param; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int err = 0; + + if (!cam) { + pr_info("Internal error, cam_data not found!\n"); + return -ENODEV; + } + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + if (signal_pending(current)) + goto oops; + + if (cam->open_count++ == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + + err = prp_enc_select(cam); + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + csi_enable_mclk(CSI_MCLK_I2C, true, true); + param = cam->cam_sensor->reset(); + if (param == NULL) { + cam->open_count--; + csi_enable_mclk(CSI_MCLK_I2C, false, false); + err = -ENODEV; + goto oops; + } + csi_param.sens_clksrc = 0; + csi_param.clk_mode = param->clk_mode; + csi_param.pixclk_pol = param->pixclk_pol; + csi_param.data_width = param->data_width; + csi_param.data_pol = param->data_pol; + csi_param.ext_vsync = param->ext_vsync; + csi_param.Vsync_pol = param->Vsync_pol; + csi_param.Hsync_pol = param->Hsync_pol; + csi_init_interface(param->width, param->height, + param->pixel_fmt, csi_param); + cam->cam_sensor->get_color(&cam->bright, &cam->saturation, + &cam->red, &cam->green, &cam->blue); + if (cam->cam_sensor->get_ae_mode) + cam->cam_sensor->get_ae_mode(&cam->ae_mode); + csi_enable_mclk(CSI_MCLK_I2C, false, false); + prp_init(cam); + + } + + file->private_data = dev; + oops: + up(&cam->busy_lock); + return err; +} + +/*! + * V4L interface - close function + * + * @param inode struct inode * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + int err = 0; + cam_data *cam = video_get_drvdata(dev); + + /* for the case somebody hit the ctrl C */ + if (cam->overlay_pid == current->pid) { + err = stop_preview(cam); + cam->overlay_on = false; + } + if (cam->capture_pid == current->pid) { + err |= mxc_streamoff(cam); + cam->capture_on = false; + wake_up_interruptible(&cam->enc_queue); + } + + if (--cam->open_count == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + pr_debug("mxc_v4l_close: release resource\n"); + + err |= prp_enc_deselect(cam); + + mxc_free_frame_buf(cam); + file->private_data = NULL; + + /* capture off */ + wake_up_interruptible(&cam->enc_queue); + mxc_free_frames(cam); + cam->enc_counter++; + prp_exit(cam); + } + + return err; +} + +#ifdef CONFIG_VIDEO_MXC_CSI_DMA +#include <mach/dma.h> + +#define CSI_DMA_STATUS_IDLE 0 /* DMA is not started */ +#define CSI_DMA_STATUS_WORKING 1 /* DMA is transfering the data */ +#define CSI_DMA_STATUS_DONE 2 /* One frame completes successfully */ +#define CSI_DMA_STATUS_ERROR 3 /* Error occurs during the DMA */ + +/* + * Sometimes the start of the DMA is not synchronized with the CSI + * SOF (Start of Frame) interrupt which will lead to incorrect + * captured image. In this case the driver will re-try capturing + * another frame. The following macro defines the maximum re-try + * times. + */ +#define CSI_DMA_RETRY 8 + +/* + * Size of the physical contiguous memory area used to hold image data + * transfered by DMA. It can be less than the size of the image data. + */ +#define CSI_MEM_SIZE (1024 * 600) + +/* Number of bytes for one DMA transfer */ +#define CSI_DMA_LENGTH (1024 * 200) + +static int g_dma_channel; +static int g_dma_status = CSI_DMA_STATUS_DONE; +static volatile int g_dma_completed; /* number of completed DMA transfers */ +static volatile int g_dma_copied; /* number of copied DMA transfers */ +static struct tasklet_struct g_dma_tasklet; +static char *g_user_buf; /* represents the buf passed by read() */ +static int g_user_count; /* represents the count passed by read() */ + +/*! + * @brief setup the DMA to transfer data + * There may be more than one DMA to transfer the whole image. Those + * DMAs work like chain. This function is used to setup the DMA in + * case there is enough space to hold the data. + * @param data pointer to the cam structure + */ +static void mxc_csi_dma_chaining(void *data) +{ + cam_data *cam = (cam_data *) data; + int count, chained = 0; + int max_dma = CSI_MEM_SIZE / CSI_DMA_LENGTH; + mxc_dma_requestbuf_t dma_request; + + while (chained * CSI_DMA_LENGTH < g_user_count) { + /* + * Calculate how many bytes the DMA should transfer. It may + * be less than CSI_DMA_LENGTH if the DMA is the last one. + */ + if ((chained + 1) * CSI_DMA_LENGTH > g_user_count) + count = g_user_count - chained * CSI_DMA_LENGTH; + else + count = CSI_DMA_LENGTH; + pr_debug("%s() DMA chained count = %d\n", __FUNCTION__, count); + + /* Config DMA */ + memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); + dma_request.dst_addr = cam->still_buf[0] + + (chained % max_dma) * CSI_DMA_LENGTH; + dma_request.src_addr = (dma_addr_t) CSI_CSIRXFIFO_PHYADDR; + dma_request.num_of_bytes = count; + mxc_dma_config(g_dma_channel, &dma_request, 1, + MXC_DMA_MODE_READ); + + chained++; + } +} + +/*! + * @brief Copy image data from physical contiguous memory to user space buffer + * Once the data are copied, there will be more spare space in the + * physical contiguous memory to receive data from DMA. + * @param data pointer to the cam structure + */ +static void mxc_csi_dma_task(unsigned long data) +{ + cam_data *cam = (cam_data *) data; + int count; + int max_dma = CSI_MEM_SIZE / CSI_DMA_LENGTH; + + while (g_dma_copied < g_dma_completed) { + /* + * Calculate how many bytes the DMA has transfered. It may + * be less than CSI_DMA_LENGTH if the DMA is the last one. + */ + if ((g_dma_copied + 1) * CSI_DMA_LENGTH > g_user_count) + count = g_user_count - g_dma_copied * CSI_DMA_LENGTH; + else + count = CSI_DMA_LENGTH; + if (copy_to_user(g_user_buf + g_dma_copied * CSI_DMA_LENGTH, + cam->still_buf_vaddr + (g_dma_copied % max_dma) + * CSI_DMA_LENGTH, count)) + pr_debug("Warning: some bytes not copied\n"); + + g_dma_copied++; + } + + /* If the whole image has been captured */ + if (g_dma_copied * CSI_DMA_LENGTH >= g_user_count) { + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + } + + pr_debug("%s() DMA completed = %d copied = %d\n", + __FUNCTION__, g_dma_completed, g_dma_copied); +} + +/*! + * @brief DMA interrupt callback function + * @param data pointer to the cam structure + * @param error DMA error flag + * @param count number of bytes transfered by the DMA + */ +static void mxc_csi_dma_callback(void *data, int error, unsigned int count) +{ + cam_data *cam = (cam_data *) data; + int max_dma = CSI_MEM_SIZE / CSI_DMA_LENGTH; + unsigned long lock_flags; + + spin_lock_irqsave(&cam->int_lock, lock_flags); + + g_dma_completed++; + + if (error != MXC_DMA_DONE) { + g_dma_status = CSI_DMA_STATUS_ERROR; + pr_debug("%s() DMA error\n", __FUNCTION__); + } + + /* If the whole image has been captured */ + if ((g_dma_status != CSI_DMA_STATUS_ERROR) + && (g_dma_completed * CSI_DMA_LENGTH >= g_user_count)) + g_dma_status = CSI_DMA_STATUS_DONE; + + if ((g_dma_status == CSI_DMA_STATUS_WORKING) && + (g_dma_completed >= g_dma_copied + max_dma)) { + g_dma_status = CSI_DMA_STATUS_ERROR; + pr_debug("%s() Previous buffer over written\n", __FUNCTION__); + } + + /* Schedule the tasklet */ + tasklet_schedule(&g_dma_tasklet); + + spin_unlock_irqrestore(&cam->int_lock, lock_flags); + + pr_debug("%s() count = %d bytes\n", __FUNCTION__, count); +} + +/*! + * @brief CSI interrupt callback function + * @param data pointer to the cam structure + * @param status CSI interrupt status + */ +static void mxc_csi_irq_callback(void *data, unsigned long status) +{ + cam_data *cam = (cam_data *) data; + unsigned long lock_flags; + + spin_lock_irqsave(&cam->int_lock, lock_flags); + + /* Wait for SOF (Start of Frame) interrupt to sync the image */ + if (status & BIT_SOF_INT) { + if (g_dma_status == CSI_DMA_STATUS_IDLE) { + /* Start DMA transfer to capture image */ + mxc_dma_enable(g_dma_channel); + g_dma_status = CSI_DMA_STATUS_WORKING; + pr_debug("%s() DMA started.\n", __FUNCTION__); + } else if (g_dma_status == CSI_DMA_STATUS_WORKING) { + /* + * Another SOF occurs during DMA transfer. In this + * case the image is not synchronized so need to + * report error and probably try again. + */ + g_dma_status = CSI_DMA_STATUS_ERROR; + pr_debug("%s() Image is not synchronized with DMA - " + "SOF before DMA completes\n", __FUNCTION__); + } + } + + spin_unlock_irqrestore(&cam->int_lock, lock_flags); + + pr_debug("%s() g_dma_status = %d\n", __FUNCTION__, g_dma_status); +} + +/*! + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t +mxc_v4l_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + int err = 0; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int retry = CSI_DMA_RETRY; + + g_user_buf = buf; + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Video capture and still image capture are exclusive */ + if (cam->capture_on == true) { + err = -EBUSY; + goto exit0; + } + + /* The CSI-DMA can not do CSC */ + if (cam->v2f.fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) { + pr_info("mxc_v4l_read support YUYV pixel format only\n"); + err = -EINVAL; + goto exit0; + } + + /* The CSI-DMA can not do resize or crop */ + if ((cam->v2f.fmt.pix.width != cam->crop_bounds.width) + || (cam->v2f.fmt.pix.height != cam->crop_bounds.height)) { + pr_info("mxc_v4l_read resize is not supported\n"); + pr_info("supported image size width = %d height = %d\n", + cam->crop_bounds.width, cam->crop_bounds.height); + err = -EINVAL; + goto exit0; + } + if ((cam->crop_current.left != cam->crop_bounds.left) + || (cam->crop_current.width != cam->crop_bounds.width) + || (cam->crop_current.top != cam->crop_bounds.top) + || (cam->crop_current.height != cam->crop_bounds.height)) { + pr_info("mxc_v4l_read cropping is not supported\n"); + err = -EINVAL; + goto exit0; + } + + cam->still_buf_vaddr = dma_alloc_coherent(0, + PAGE_ALIGN(CSI_MEM_SIZE), + &cam->still_buf[0], + GFP_DMA | GFP_KERNEL); + + if (!cam->still_buf_vaddr) { + pr_info("mxc_v4l_read failed at allocate still_buf\n"); + err = -ENOBUFS; + goto exit0; + } + + /* Initialize DMA */ + g_dma_channel = mxc_dma_request(MXC_DMA_CSI_RX, "CSI RX DMA"); + if (g_dma_channel < 0) { + pr_debug("mxc_v4l_read failed to request DMA channel\n"); + err = -EIO; + goto exit1; + } + + err = mxc_dma_callback_set(g_dma_channel, + (mxc_dma_callback_t) mxc_csi_dma_callback, + (void *)cam); + if (err != 0) { + pr_debug("mxc_v4l_read failed to set DMA callback\n"); + err = -EIO; + goto exit2; + } + + g_user_buf = buf; + if (cam->v2f.fmt.pix.sizeimage < count) + g_user_count = cam->v2f.fmt.pix.sizeimage; + else + g_user_count = count & ~0x3; + + tasklet_init(&g_dma_tasklet, mxc_csi_dma_task, (unsigned long)cam); + g_dma_status = CSI_DMA_STATUS_DONE; + csi_set_callback(mxc_csi_irq_callback, cam); + csi_enable_prpif(0); + + /* clear current SOF first */ + csi_clear_status(BIT_SOF_INT); + csi_enable_mclk(CSI_MCLK_RAW, true, true); + + do { + g_dma_completed = g_dma_copied = 0; + mxc_csi_dma_chaining(cam); + cam->still_counter = 0; + g_dma_status = CSI_DMA_STATUS_IDLE; + + if (!wait_event_interruptible_timeout(cam->still_queue, + cam->still_counter != 0, + 10 * HZ)) { + pr_info("mxc_v4l_read timeout counter %x\n", + cam->still_counter); + err = -ETIME; + goto exit3; + } + + if (g_dma_status == CSI_DMA_STATUS_DONE) + break; + + if (retry-- == 0) + break; + + pr_debug("Now retry image capture\n"); + } while (1); + + if (g_dma_status != CSI_DMA_STATUS_DONE) + err = -EIO; + + exit3: + csi_enable_prpif(1); + g_dma_status = CSI_DMA_STATUS_DONE; + csi_set_callback(0, 0); + csi_enable_mclk(CSI_MCLK_RAW, false, false); + tasklet_kill(&g_dma_tasklet); + + exit2: + mxc_dma_free(g_dma_channel); + + exit1: + dma_free_coherent(0, PAGE_ALIGN(CSI_MEM_SIZE), + cam->still_buf_vaddr, cam->still_buf[0]); + cam->still_buf[0] = 0; + + exit0: + up(&cam->busy_lock); + if (err < 0) + return err; + else + return g_user_count; +} +#else +/*! + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t +mxc_v4l_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + int err = 0; + u8 *v_address; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Video capture and still image capture are exclusive */ + if (cam->capture_on == true) { + err = -EBUSY; + goto exit0; + } + + v_address = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->still_buf[0], + GFP_DMA | GFP_KERNEL); + + if (!v_address) { + pr_info("mxc_v4l_read failed at allocate still_buf\n"); + err = -ENOBUFS; + goto exit0; + } + + if (prp_still_select(cam)) { + err = -EIO; + goto exit1; + } + + cam->still_counter = 0; + if (cam->csi_start(cam)) { + err = -EIO; + goto exit2; + } + + if (!wait_event_interruptible_timeout(cam->still_queue, + cam->still_counter != 0, + 10 * HZ)) { + pr_info("mxc_v4l_read timeout counter %x\n", + cam->still_counter); + err = -ETIME; + goto exit2; + } + err = copy_to_user(buf, v_address, cam->v2f.fmt.pix.sizeimage); + + exit2: + prp_still_deselect(cam); + + exit1: + dma_free_coherent(0, cam->v2f.fmt.pix.sizeimage, v_address, + cam->still_buf[0]); + cam->still_buf[0] = 0; + + exit0: + up(&cam->busy_lock); + if (err < 0) + return err; + else + return (cam->v2f.fmt.pix.sizeimage - err); +} +#endif /* CONFIG_VIDEO_MXC_CSI_DMA */ + +/*! + * V4L interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int retval = 0; + unsigned long lock_flags; + + if (!cam) + return -EBADF; + + wait_event_interruptible(cam->power_queue, cam->low_power == false); + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + /*! + * V4l2 VIDIOC_QUERYCAP ioctl + */ + case VIDIOC_QUERYCAP:{ + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2"); + cap->version = KERNEL_VERSION(0, 1, 11); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_STREAMING + | V4L2_CAP_READWRITE; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + + /*! + * V4l2 VIDIOC_G_FMT ioctl + */ + case VIDIOC_G_FMT:{ + struct v4l2_format *gf = arg; + retval = mxc_v4l2_g_fmt(cam, gf); + break; + } + + /*! + * V4l2 VIDIOC_S_FMT ioctl + */ + case VIDIOC_S_FMT:{ + struct v4l2_format *sf = arg; + retval = mxc_v4l2_s_fmt(cam, sf); + break; + } + + /*! + * V4l2 VIDIOC_REQBUFS ioctl + */ + case VIDIOC_REQBUFS:{ + struct v4l2_requestbuffers *req = arg; + if (req->count > FRAME_NUM) { + pr_info("VIDIOC_REQBUFS: not enough buffer\n"); + req->count = FRAME_NUM; + } + + if ((req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (req->memory != V4L2_MEMORY_MMAP)) { + pr_debug("VIDIOC_REQBUFS: wrong buffer type\n"); + retval = -EINVAL; + break; + } + + mxc_streamoff(cam); + mxc_free_frame_buf(cam); + + retval = mxc_allocate_frame_buf(cam, req->count); + break; + } + + /*! + * V4l2 VIDIOC_QUERYBUF ioctl + */ + case VIDIOC_QUERYBUF:{ + struct v4l2_buffer *buf = arg; + int index = buf->index; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_debug + ("VIDIOC_QUERYBUFS: wrong buffer type\n"); + retval = -EINVAL; + break; + } + + memset(buf, 0, sizeof(buf)); + buf->index = index; + + down(&cam->param_lock); + retval = mxc_v4l2_buffer_status(cam, buf); + up(&cam->param_lock); + break; + } + + /*! + * V4l2 VIDIOC_QBUF ioctl + */ + case VIDIOC_QBUF:{ + struct v4l2_buffer *buf = arg; + int index = buf->index; + + pr_debug("VIDIOC_QBUF: %d\n", buf->index); + + spin_lock_irqsave(&cam->int_lock, lock_flags); + if ((cam->frame[index].buffer.flags & 0x7) == + V4L2_BUF_FLAG_MAPPED) { + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + if (cam->skip_frame > 0) { + list_add_tail(&cam->frame[index].queue, + &cam->working_q); + retval = + cam->enc_update_eba(cam-> + frame[index]. + paddress, + &cam-> + ping_pong_csi); + cam->skip_frame = 0; + } else { + list_add_tail(&cam->frame[index].queue, + &cam->ready_q); + } + } else if (cam->frame[index].buffer.flags & + V4L2_BUF_FLAG_QUEUED) { + pr_debug + ("VIDIOC_QBUF: buffer already queued\n"); + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_DONE) { + pr_debug + ("VIDIOC_QBUF: overwrite done buffer.\n"); + cam->frame[index].buffer.flags &= + ~V4L2_BUF_FLAG_DONE; + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + } + buf->flags = cam->frame[index].buffer.flags; + spin_unlock_irqrestore(&cam->int_lock, lock_flags); + break; + } + + /*! + * V4l2 VIDIOC_DQBUF ioctl + */ + case VIDIOC_DQBUF:{ + struct v4l2_buffer *buf = arg; + + retval = mxc_v4l_dqueue(cam, buf); + + break; + } + + /*! + * V4l2 VIDIOC_STREAMON ioctl + */ + case VIDIOC_STREAMON:{ + cam->capture_on = true; + retval = mxc_streamon(cam); + break; + } + + /*! + * V4l2 VIDIOC_STREAMOFF ioctl + */ + case VIDIOC_STREAMOFF:{ + retval = mxc_streamoff(cam); + cam->capture_on = false; + break; + } + + /*! + * V4l2 VIDIOC_G_CTRL ioctl + */ + case VIDIOC_G_CTRL:{ + retval = mxc_get_v42l_control(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_S_CTRL ioctl + */ + case VIDIOC_S_CTRL:{ + retval = mxc_set_v42l_control(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_CROPCAP ioctl + */ + case VIDIOC_CROPCAP:{ + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + cap->bounds = cam->crop_bounds; + cap->defrect = cam->crop_defrect; + break; + } + + /*! + * V4l2 VIDIOC_G_CROP ioctl + */ + case VIDIOC_G_CROP:{ + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + crop->c = cam->crop_current; + break; + } + + /*! + * V4l2 VIDIOC_S_CROP ioctl + */ + case VIDIOC_S_CROP:{ + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = &cam->crop_bounds; + int i; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + + crop->c.top = (crop->c.top < b->top) ? b->top + : crop->c.top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top + b->height - crop->c.top) + crop->c.height = + b->top + b->height - crop->c.top; + + crop->c.left = (crop->c.left < b->left) ? b->left + : crop->c.left; + if (crop->c.left > b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + crop->c.width &= ~0x1; + + /* + * MX27 PrP limitation: + * The right spare space (CSI_FRAME_X_SIZE + * - SOURCE_LINE_STRIDE - PICTURE_X_SIZE)) must be + * multiple of 32. + * So we tune the crop->c.left value to the closest + * desired cropping value and meet the PrP requirement. + */ + i = ((b->left + b->width) + - (crop->c.left + crop->c.width)) % 32; + if (i <= 16) { + if (crop->c.left + crop->c.width + i + <= b->left + b->width) + crop->c.left += i; + else if (crop->c.left - (32 - i) >= b->left) + crop->c.left -= 32 - i; + else { + retval = -EINVAL; + break; + } + } else { + if (crop->c.left - (32 - i) >= b->left) + crop->c.left -= 32 - i; + else if (crop->c.left + crop->c.width + i + <= b->left + b->width) + crop->c.left += i; + else { + retval = -EINVAL; + break; + } + } + + cam->crop_current = crop->c; + + break; + } + + /*! + * V4l2 VIDIOC_OVERLAY ioctl + */ + case VIDIOC_OVERLAY:{ + int *on = arg; + if (*on) { + cam->overlay_on = true; + retval = start_preview(cam); + } + if (!*on) { + retval = stop_preview(cam); + cam->overlay_on = false; + } + break; + } + + /*! + * V4l2 VIDIOC_G_FBUF ioctl + */ + case VIDIOC_G_FBUF:{ + struct v4l2_framebuffer *fb = arg; + struct fb_var_screeninfo *var; + + if (cam->output >= num_registered_fb) { + retval = -EINVAL; + break; + } + + var = ®istered_fb[cam->output]->var; + cam->v4l2_fb.fmt.width = var->xres; + cam->v4l2_fb.fmt.height = var->yres; + cam->v4l2_fb.fmt.bytesperline = + var->xres_virtual * var->bits_per_pixel; + cam->v4l2_fb.fmt.colorspace = V4L2_COLORSPACE_SRGB; + *fb = cam->v4l2_fb; + break; + } + + /*! + * V4l2 VIDIOC_S_FBUF ioctl + */ + case VIDIOC_S_FBUF:{ + struct v4l2_framebuffer *fb = arg; + cam->v4l2_fb.flags = fb->flags; + cam->v4l2_fb.fmt.pixelformat = fb->fmt.pixelformat; + break; + } + + case VIDIOC_G_PARM:{ + struct v4l2_streamparm *parm = arg; + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_debug("VIDIOC_G_PARM invalid type\n"); + retval = -EINVAL; + break; + } + parm->parm.capture = cam->streamparm.parm.capture; + break; + } + case VIDIOC_S_PARM:{ + struct v4l2_streamparm *parm = arg; + retval = mxc_v4l2_s_param(cam, parm); + break; + } + + /* linux v4l2 bug, kernel c0485619 user c0405619 */ + case VIDIOC_ENUMSTD:{ + struct v4l2_standard *e = arg; + *e = cam->standard; + pr_debug("VIDIOC_ENUMSTD call\n"); + retval = 0; + break; + } + + case VIDIOC_G_STD:{ + v4l2_std_id *e = arg; + *e = cam->standard.id; + break; + } + + case VIDIOC_S_STD:{ + break; + } + + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if (output->index >= num_registered_fb) { + retval = -EINVAL; + break; + } + + strncpy(output->name, + registered_fb[output->index]->fix.id, 31); + output->type = V4L2_OUTPUT_TYPE_ANALOG; + output->audioset = 0; + output->modulator = 0; + output->std = V4L2_STD_UNKNOWN; + + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = cam->output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + + if (*p_output_num >= num_registered_fb) { + retval = -EINVAL; + break; + } + + cam->output = *p_output_num; + break; + } + + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_ENUMINPUT: + case VIDIOC_G_INPUT: + case VIDIOC_S_INPUT: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&cam->busy_lock); + return retval; +} + +/* + * V4L interface - ioctl function + * + * @return None + */ +static int +mxc_v4l_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l_do_ioctl); +} + +/*! + * V4L interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = video_devdata(file); + unsigned long size; + int res = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_debug("mxc_mmap: remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mxc_mmap_exit: + up(&cam->busy_lock); + return res; +} + +/*! + * V4L interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + queue = &cam->enc_queue; + poll_wait(file, queue, wait); + + up(&cam->busy_lock); + return res; +} + +static struct +file_operations mxc_v4l_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l_open, + .release = mxc_v4l_close, + .read = mxc_v4l_read, + .ioctl = mxc_v4l_ioctl, + .mmap = mxc_mmap, + .poll = mxc_poll, +}; + +static struct video_device mxc_v4l_template = { + .name = "Mxc Camera", + .vfl_type = VID_TYPE_CAPTURE, + .fops = &mxc_v4l_fops, + .release = video_device_release, +}; + +static void camera_platform_release(struct device *device) +{ +} + +/*! Device Definition for Mt9v111 devices */ +static struct platform_device mxc_v4l2_devices = { + .name = "mxc_v4l2", + .dev = { + .release = camera_platform_release, + }, + .id = 0, +}; + +extern struct camera_sensor camera_sensor_if; + +/*! +* Camera V4l2 callback function. +* +* @return status +*/ +static void camera_callback(u32 mask, void *dev) +{ + struct mxc_v4l_frame *done_frame; + struct mxc_v4l_frame *ready_frame; + + cam_data *cam = (cam_data *) dev; + if (cam == NULL) + return; + + if (list_empty(&cam->working_q)) { + printk(KERN_ERR "camera_callback: working queue empty\n"); + return; + } + + done_frame = + list_entry(cam->working_q.next, struct mxc_v4l_frame, queue); + if (done_frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + done_frame->buffer.flags |= V4L2_BUF_FLAG_DONE; + done_frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + + if (list_empty(&cam->ready_q)) { + cam->skip_frame++; + } else { + ready_frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, + queue); + list_del(cam->ready_q.next); + list_add_tail(&ready_frame->queue, &cam->working_q); + cam->enc_update_eba(ready_frame->paddress, + &cam->ping_pong_csi); + } + + /* Added to the done queue */ + list_del(cam->working_q.next); + list_add_tail(&done_frame->queue, &cam->done_q); + + /* Wake up the queue */ + cam->enc_counter++; + wake_up_interruptible(&cam->enc_queue); + } else { + printk(KERN_ERR "camera_callback :buffer not queued\n"); + } +} + +/*! + * initialize cam_data structure + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static void init_camera_struct(cam_data *cam) +{ + int i; + + /* Default everything to 0 */ + memset(cam, 0, sizeof(cam_data)); + + init_MUTEX(&cam->param_lock); + init_MUTEX(&cam->busy_lock); + + cam->video_dev = video_device_alloc(); + if (cam->video_dev == NULL) + return; + + *(cam->video_dev) = mxc_v4l_template; + + video_set_drvdata(cam->video_dev, cam); + dev_set_drvdata(&mxc_v4l2_devices.dev, (void *)cam); + cam->video_dev->minor = -1; + + for (i = 0; i < FRAME_NUM; i++) { + cam->frame[i].width = 0; + cam->frame[i].height = 0; + cam->frame[i].paddress = 0; + } + + init_waitqueue_head(&cam->enc_queue); + init_waitqueue_head(&cam->still_queue); + + /* setup cropping */ + cam->crop_bounds.left = 0; + cam->crop_bounds.width = 640; + cam->crop_bounds.top = 0; + cam->crop_bounds.height = 480; + cam->crop_current = cam->crop_defrect = cam->crop_bounds; + cam->streamparm.parm.capture.capturemode = 0; + + cam->standard.index = 0; + cam->standard.id = V4L2_STD_UNKNOWN; + cam->standard.frameperiod.denominator = 30; + cam->standard.frameperiod.numerator = 1; + cam->standard.framelines = 480; + cam->streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->streamparm.parm.capture.timeperframe = cam->standard.frameperiod; + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + cam->overlay_on = false; + cam->capture_on = false; + cam->skip_frame = 0; + cam->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + cam->v4l2_fb.flags = V4L2_FBUF_FLAG_PRIMARY; + + cam->v2f.fmt.pix.sizeimage = 352 * 288 * 3 / 2; + cam->v2f.fmt.pix.bytesperline = 288 * 3 / 2; + cam->v2f.fmt.pix.width = 288; + cam->v2f.fmt.pix.height = 352; + cam->v2f.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + cam->win.w.width = 160; + cam->win.w.height = 160; + cam->win.w.left = 0; + cam->win.w.top = 0; + + cam->cam_sensor = &camera_sensor_if; + cam->enc_callback = camera_callback; + + init_waitqueue_head(&cam->power_queue); + cam->int_lock = __SPIN_LOCK_UNLOCKED(cam->int_lock); + spin_lock_init(&cam->int_lock); +} + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +/*! + * camera_power function + * Turn Sensor power On/Off + * + * @param cameraOn true to turn camera on, otherwise shut down + * + * @return status + */ +static u8 camera_power(bool cameraOn) +{ + if (cameraOn == true) { + gpio_sensor_active(); + csi_enable_mclk(csi_mclk_flag_backup, true, true); + } else { + csi_mclk_flag_backup = csi_read_mclk_flag(); + csi_enable_mclk(csi_mclk_flag_backup, false, false); + gpio_sensor_inactive(); + } + return 0; +} + +/*! + * This function is called to put the sensor in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure used to give information on which I2C + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxc_v4l2_suspend(struct platform_device *pdev, pm_message_t state) +{ + cam_data *cam = platform_get_drvdata(pdev); + + if (cam == NULL) { + return -1; + } + + cam->low_power = true; + + if (cam->overlay_on == true) + stop_preview(cam); + if ((cam->capture_on == true) && cam->enc_disable) { + cam->enc_disable(cam); + } + camera_power(false); + + return 0; +} + +/*! + * This function is called to bring the sensor back from a low power state.Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param pdev the device structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxc_v4l2_resume(struct platform_device *pdev) +{ + cam_data *cam = platform_get_drvdata(pdev); + + if (cam == NULL) { + return -1; + } + + cam->low_power = false; + wake_up_interruptible(&cam->power_queue); + + if (cam->overlay_on == true) + start_preview(cam); + if (cam->capture_on == true) + mxc_streamon(cam); + camera_power(true); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2_driver = { + .driver = { + .name = "mxc_v4l2", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, + .probe = NULL, + .remove = NULL, + .suspend = mxc_v4l2_suspend, + .resume = mxc_v4l2_resume, + .shutdown = NULL, +}; + +/*! + * Entry point for the V4L2 + * + * @return Error code indicating success or failure + */ +static __init int camera_init(void) +{ + u8 err = 0; + cam_data *cam; + + g_cam = kmalloc(sizeof(cam_data), GFP_KERNEL); + if (g_cam == NULL) { + pr_debug("failed to mxc_v4l_register_camera\n"); + return -1; + } + + cam = g_cam; + init_camera_struct(cam); + + /* Register the I2C device */ + err = platform_device_register(&mxc_v4l2_devices); + if (err != 0) { + pr_debug("camera_init: platform_device_register failed.\n"); + video_device_release(cam->video_dev); + kfree(cam); + g_cam = NULL; + } + + /* Register the device driver structure. */ + err = platform_driver_register(&mxc_v4l2_driver); + if (err != 0) { + platform_device_unregister(&mxc_v4l2_devices); + pr_debug("camera_init: driver_register failed.\n"); + video_device_release(cam->video_dev); + kfree(cam); + g_cam = NULL; + return err; + } + + /* register v4l device */ + if (video_register_device(cam->video_dev, VFL_TYPE_GRABBER, video_nr) + == -1) { + platform_driver_unregister(&mxc_v4l2_driver); + platform_device_unregister(&mxc_v4l2_devices); + video_device_release(cam->video_dev); + kfree(cam); + g_cam = NULL; + pr_debug("video_register_device failed\n"); + return -1; + } + + return err; +} + +/*! + * Exit and cleanup for the V4L2 + * + */ +static void __exit camera_exit(void) +{ + pr_debug("unregistering video\n"); + + video_unregister_device(g_cam->video_dev); + + platform_driver_unregister(&mxc_v4l2_driver); + platform_device_unregister(&mxc_v4l2_devices); + + if (g_cam->open_count) { + pr_debug("camera open -- setting ops to NULL\n"); + } else { + pr_debug("freeing camera\n"); + mxc_free_frame_buf(g_cam); + kfree(g_cam); + g_cam = NULL; + } +} + +module_init(camera_init); +module_exit(camera_exit); + +module_param(video_nr, int, 0444); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2 capture driver for Mxc based cameras"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/capture/fsl_csi.c b/drivers/media/video/mxc/capture/fsl_csi.c new file mode 100644 index 000000000000..6dd0cc5a59d8 --- /dev/null +++ b/drivers/media/video/mxc/capture/fsl_csi.c @@ -0,0 +1,289 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_csi.c, this file is derived from mx27_csi.c + * + * @brief mx25 CMOS Sensor interface functions + * + * @ingroup CSI + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <mach/clock.h> +#include <mach/hardware.h> + +#include "mxc_v4l2_capture.h" +#include "fsl_csi.h" + +static bool g_csi_mclk_on; +static csi_irq_callback_t g_callback; +static void *g_callback_data; +static struct clk csi_mclk; + +static irqreturn_t csi_irq_handler(int irq, void *data) +{ + cam_data *cam = (cam_data *) data; + unsigned long status = __raw_readl(CSI_CSISR); + unsigned long cr3 = __raw_readl(CSI_CSICR3); + unsigned int frame_count = (cr3 >> 16) & 0xFFFF; + + __raw_writel(status, CSI_CSISR); + + if (status & BIT_SOF_INT) { + /* reflash the embeded DMA controller */ + if (frame_count % 2 == 1) + __raw_writel(cr3 | BIT_DMA_REFLASH_RFF, CSI_CSICR3); + } + + if (status & BIT_DMA_TSF_DONE_FB1) { + if (cam->capture_on) { + cam->ping_pong_csi = 1; + cam->enc_callback(0, cam); + } else { + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + } + } + + if (status & BIT_DMA_TSF_DONE_FB2) { + if (cam->capture_on) { + cam->ping_pong_csi = 2; + cam->enc_callback(0, cam); + } else { + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + } + } + + if (g_callback) + g_callback(g_callback_data, status); + + pr_debug("CSI status = 0x%08lX\n", status); + + return IRQ_HANDLED; +} + +static void csihw_reset_frame_count(void) +{ + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_FRMCNT_RST, CSI_CSICR3); +} + +static void csihw_reset(void) +{ + csihw_reset_frame_count(); + __raw_writel(CSICR1_RESET_VAL, CSI_CSICR1); + __raw_writel(CSICR2_RESET_VAL, CSI_CSICR2); + __raw_writel(CSICR3_RESET_VAL, CSI_CSICR3); +} + +/*! + * csi_init_interface + * Init csi interface + */ +void csi_init_interface(void) +{ + unsigned int val = 0; + unsigned int imag_para; + + val |= BIT_SOF_POL; + val |= BIT_REDGE; + val |= BIT_GCLK_MODE; + val |= BIT_HSYNC_POL; + val |= BIT_PACK_DIR; + val |= BIT_FCC; + val |= BIT_SWAP16_EN; + val |= 1 << SHIFT_MCLKDIV; + __raw_writel(val, CSI_CSICR1); + + imag_para = (640 << 16) | 960; + __raw_writel(imag_para, CSI_CSIIMAG_PARA); + + val = 0x1010; + val |= BIT_DMA_REFLASH_RFF; + __raw_writel(val, CSI_CSICR3); +} +EXPORT_SYMBOL(csi_init_interface); + +/*! + * csi_enable_mclk + * + * @param src enum define which source to control the clk + * CSI_MCLK_VF CSI_MCLK_ENC CSI_MCLK_RAW CSI_MCLK_I2C + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return 0 for success + */ +int32_t csi_enable_mclk(int src, bool flag, bool wait) +{ + if (flag == true) { + csi_mclk_enable(); + if (wait == true) + msleep(10); + pr_debug("Enable csi clock from source %d\n", src); + g_csi_mclk_on = true; + } else { + csi_mclk_disable(); + pr_debug("Disable csi clock from source %d\n", src); + g_csi_mclk_on = false; + } + + return 0; +} +EXPORT_SYMBOL(csi_enable_mclk); + +/*! + * csi_read_mclk_flag + * + * @return gcsi_mclk_source + */ +int csi_read_mclk_flag(void) +{ + return 0; +} +EXPORT_SYMBOL(csi_read_mclk_flag); + +void csi_start_callback(void *data) +{ + cam_data *cam = (cam_data *) data; + + if (request_irq(MXC_INT_CSI, csi_irq_handler, 0, "csi", cam) < 0) + pr_debug("CSI error: irq request fail\n"); + +} +EXPORT_SYMBOL(csi_start_callback); + +void csi_stop_callback(void *data) +{ + cam_data *cam = (cam_data *) data; + + free_irq(MXC_INT_CSI, cam); +} +EXPORT_SYMBOL(csi_stop_callback); + +void csi_enable_int(int arg) +{ + unsigned long cr1 = __raw_readl(CSI_CSICR1); + + cr1 |= BIT_SOF_INTEN; + if (arg == 1) { + /* still capture needs DMA intterrupt */ + cr1 |= BIT_FB1_DMA_DONE_INTEN; + cr1 |= BIT_FB2_DMA_DONE_INTEN; + } + __raw_writel(cr1, CSI_CSICR1); +} +EXPORT_SYMBOL(csi_enable_int); + +void csi_disable_int(void) +{ + unsigned long cr1 = __raw_readl(CSI_CSICR1); + + cr1 &= ~BIT_SOF_INTEN; + cr1 &= ~BIT_FB1_DMA_DONE_INTEN; + cr1 &= ~BIT_FB2_DMA_DONE_INTEN; + __raw_writel(cr1, CSI_CSICR1); +} +EXPORT_SYMBOL(csi_disable_int); + +void csi_set_16bit_imagpara(int width, int height) +{ + int imag_para = 0; + unsigned long cr3 = __raw_readl(CSI_CSICR3); + + imag_para = (width << 16) | (height * 2); + __raw_writel(imag_para, CSI_CSIIMAG_PARA); + + /* reflash the embeded DMA controller */ + __raw_writel(cr3 | BIT_DMA_REFLASH_RFF, CSI_CSICR3); +} +EXPORT_SYMBOL(csi_set_16bit_imagpara); + +void csi_set_12bit_imagpara(int width, int height) +{ + int imag_para = 0; + unsigned long cr3 = __raw_readl(CSI_CSICR3); + + imag_para = (width << 16) | (height * 3 / 2); + __raw_writel(imag_para, CSI_CSIIMAG_PARA); + + /* reflash the embeded DMA controller */ + __raw_writel(cr3 | BIT_DMA_REFLASH_RFF, CSI_CSICR3); +} +EXPORT_SYMBOL(csi_set_12bit_imagpara); + +static void csi_mclk_recalc(struct clk *clk) +{ + u32 div; + + div = (__raw_readl(CSI_CSICR1) & BIT_MCLKDIV) >> SHIFT_MCLKDIV; + if (div == 0) + div = 1; + else + div = div * 2; + + clk->rate = clk->parent->rate / div; +} + +void csi_mclk_enable(void) +{ + __raw_writel(__raw_readl(CSI_CSICR1) | BIT_MCLKEN, CSI_CSICR1); +} + +void csi_mclk_disable(void) +{ + __raw_writel(__raw_readl(CSI_CSICR1) & ~BIT_MCLKEN, CSI_CSICR1); +} + +int32_t __init csi_init_module(void) +{ + int ret = 0; + struct clk *per_clk; + + csihw_reset(); + csi_init_interface(); + + per_clk = clk_get(NULL, "csi_clk"); + if (IS_ERR(per_clk)) + return PTR_ERR(per_clk); + + clk_put(per_clk); + csi_mclk.name = "csi_mclk"; + csi_mclk.parent = per_clk; + clk_register(&csi_mclk); + clk_enable(per_clk); + csi_mclk_recalc(&csi_mclk); + + return ret; +} + +void __exit csi_cleanup_module(void) +{ + clk_disable(&csi_mclk); + clk_unregister(&csi_mclk); +} + +module_init(csi_init_module); +module_exit(csi_cleanup_module); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("fsl CSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/fsl_csi.h b/drivers/media/video/mxc/capture/fsl_csi.h new file mode 100644 index 000000000000..ee57597407f3 --- /dev/null +++ b/drivers/media/video/mxc/capture/fsl_csi.h @@ -0,0 +1,198 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file fsl_csi.h + * + * @brief mx25 CMOS Sensor interface functions + * + * @ingroup CSI + */ + +#ifndef MX25_CSI_H +#define MX25_CSI_H + +#include <linux/io.h> +#include <mach/hardware.h> + +/* reset values */ +#define CSICR1_RESET_VAL 0x40000800 +#define CSICR2_RESET_VAL 0x0 +#define CSICR3_RESET_VAL 0x0 + +/* csi control reg 1 */ +#define BIT_SWAP16_EN (0x1 << 31) +#define BIT_EXT_VSYNC (0x1 << 30) +#define BIT_EOF_INT_EN (0x1 << 29) +#define BIT_PRP_IF_EN (0x1 << 28) +#define BIT_CCIR_MODE (0x1 << 27) +#define BIT_COF_INT_EN (0x1 << 26) +#define BIT_SF_OR_INTEN (0x1 << 25) +#define BIT_RF_OR_INTEN (0x1 << 24) +#define BIT_SFF_DMA_DONE_INTEN (0x1 << 22) +#define BIT_STATFF_INTEN (0x1 << 21) +#define BIT_FB2_DMA_DONE_INTEN (0x1 << 20) +#define BIT_FB1_DMA_DONE_INTEN (0x1 << 19) +#define BIT_RXFF_INTEN (0x1 << 18) +#define BIT_SOF_POL (0x1 << 17) +#define BIT_SOF_INTEN (0x1 << 16) +#define BIT_MCLKDIV (0xF << 12) +#define BIT_HSYNC_POL (0x1 << 11) +#define BIT_CCIR_EN (0x1 << 10) +#define BIT_MCLKEN (0x1 << 9) +#define BIT_FCC (0x1 << 8) +#define BIT_PACK_DIR (0x1 << 7) +#define BIT_CLR_STATFIFO (0x1 << 6) +#define BIT_CLR_RXFIFO (0x1 << 5) +#define BIT_GCLK_MODE (0x1 << 4) +#define BIT_INV_DATA (0x1 << 3) +#define BIT_INV_PCLK (0x1 << 2) +#define BIT_REDGE (0x1 << 1) +#define BIT_PIXEL_BIT (0x1 << 0) + +#define SHIFT_MCLKDIV 12 + +/* control reg 3 */ +#define BIT_FRMCNT (0xFFFF << 16) +#define BIT_FRMCNT_RST (0x1 << 15) +#define BIT_DMA_REFLASH_RFF (0x1 << 14) +#define BIT_DMA_REFLASH_SFF (0x1 << 13) +#define BIT_DMA_REQ_EN_RFF (0x1 << 12) +#define BIT_DMA_REQ_EN_SFF (0x1 << 11) +#define BIT_STATFF_LEVEL (0x7 << 8) +#define BIT_HRESP_ERR_EN (0x1 << 7) +#define BIT_RXFF_LEVEL (0x7 << 4) +#define BIT_TWO_8BIT_SENSOR (0x1 << 3) +#define BIT_ZERO_PACK_EN (0x1 << 2) +#define BIT_ECC_INT_EN (0x1 << 1) +#define BIT_ECC_AUTO_EN (0x1 << 0) + +#define SHIFT_FRMCNT 16 + +/* csi status reg */ +#define BIT_SFF_OR_INT (0x1 << 25) +#define BIT_RFF_OR_INT (0x1 << 24) +#define BIT_DMA_TSF_DONE_SFF (0x1 << 22) +#define BIT_STATFF_INT (0x1 << 21) +#define BIT_DMA_TSF_DONE_FB2 (0x1 << 20) +#define BIT_DMA_TSF_DONE_FB1 (0x1 << 19) +#define BIT_RXFF_INT (0x1 << 18) +#define BIT_EOF_INT (0x1 << 17) +#define BIT_SOF_INT (0x1 << 16) +#define BIT_F2_INT (0x1 << 15) +#define BIT_F1_INT (0x1 << 14) +#define BIT_COF_INT (0x1 << 13) +#define BIT_HRESP_ERR_INT (0x1 << 7) +#define BIT_ECC_INT (0x1 << 1) +#define BIT_DRDY (0x1 << 0) + +#define CSI_MCLK_VF 1 +#define CSI_MCLK_ENC 2 +#define CSI_MCLK_RAW 4 +#define CSI_MCLK_I2C 8 +#endif + +#define CSI_CSICR1 (IO_ADDRESS(CSI_BASE_ADDR)) +#define CSI_CSICR2 (IO_ADDRESS(CSI_BASE_ADDR + 0x4)) +#define CSI_CSICR3 (IO_ADDRESS(CSI_BASE_ADDR + 0x8)) +#define CSI_STATFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0xC)) +#define CSI_CSIRXFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0x10)) +#define CSI_CSIRXCNT (IO_ADDRESS(CSI_BASE_ADDR + 0x14)) +#define CSI_CSISR (IO_ADDRESS(CSI_BASE_ADDR + 0x18)) + +#define CSI_CSIDBG (IO_ADDRESS(CSI_BASE_ADDR + 0x1C)) +#define CSI_CSIDMASA_STATFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0x20)) +#define CSI_CSIDMATS_STATFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0x24)) +#define CSI_CSIDMASA_FB1 (IO_ADDRESS(CSI_BASE_ADDR + 0x28)) +#define CSI_CSIDMASA_FB2 (IO_ADDRESS(CSI_BASE_ADDR + 0x2C)) +#define CSI_CSIFBUF_PARA (IO_ADDRESS(CSI_BASE_ADDR + 0x30)) +#define CSI_CSIIMAG_PARA (IO_ADDRESS(CSI_BASE_ADDR + 0x34)) + +#define CSI_CSIRXFIFO_PHYADDR (CSI_BASE_ADDR + 0x10) + +static inline void csi_clear_status(unsigned long status) +{ + __raw_writel(status, CSI_CSISR); +} + +struct csi_signal_cfg_t { + unsigned data_width:3; + unsigned clk_mode:2; + unsigned ext_vsync:1; + unsigned Vsync_pol:1; + unsigned Hsync_pol:1; + unsigned pixclk_pol:1; + unsigned data_pol:1; + unsigned sens_clksrc:1; +}; + +struct csi_config_t { + /* control reg 1 */ + unsigned int swap16_en:1; + unsigned int ext_vsync:1; + unsigned int eof_int_en:1; + unsigned int prp_if_en:1; + unsigned int ccir_mode:1; + unsigned int cof_int_en:1; + unsigned int sf_or_inten:1; + unsigned int rf_or_inten:1; + unsigned int sff_dma_done_inten:1; + unsigned int statff_inten:1; + unsigned int fb2_dma_done_inten:1; + unsigned int fb1_dma_done_inten:1; + unsigned int rxff_inten:1; + unsigned int sof_pol:1; + unsigned int sof_inten:1; + unsigned int mclkdiv:4; + unsigned int hsync_pol:1; + unsigned int ccir_en:1; + unsigned int mclken:1; + unsigned int fcc:1; + unsigned int pack_dir:1; + unsigned int gclk_mode:1; + unsigned int inv_data:1; + unsigned int inv_pclk:1; + unsigned int redge:1; + unsigned int pixel_bit:1; + + /* control reg 3 */ + unsigned int frmcnt:16; + unsigned int frame_reset:1; + unsigned int dma_reflash_rff:1; + unsigned int dma_reflash_sff:1; + unsigned int dma_req_en_rff:1; + unsigned int dma_req_en_sff:1; + unsigned int statff_level:3; + unsigned int hresp_err_en:1; + unsigned int rxff_level:3; + unsigned int two_8bit_sensor:1; + unsigned int zero_pack_en:1; + unsigned int ecc_int_en:1; + unsigned int ecc_auto_en:1; + /* fifo counter */ + unsigned int rxcnt; +}; + +typedef void (*csi_irq_callback_t) (void *data, unsigned long status); + +int32_t csi_enable_mclk(int src, bool flag, bool wait); +void csi_init_interface(void); +void csi_set_16bit_imagpara(int width, int height); +void csi_set_12bit_imagpara(int width, int height); +int csi_read_mclk_flag(void); +void csi_start_callback(void *data); +void csi_stop_callback(void *data); +void csi_enable_int(int arg); +void csi_disable_int(void); +void csi_mclk_enable(void); +void csi_mclk_disable(void); diff --git a/drivers/media/video/mxc/capture/ipu_csi_enc.c b/drivers/media/video/mxc/capture/ipu_csi_enc.c new file mode 100644 index 000000000000..c0842f81ee27 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_csi_enc.c @@ -0,0 +1,332 @@ +/* + * Copyright 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_csi_enc.c + * + * @brief CSI Use case for video capture + * + * @ingroup IPU + */ + +#include <linux/dma-mapping.h> +#include <linux/ipu.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +#ifdef CAMERA_DBG + #define CAMERA_TRACE(x) (printk)x +#else + #define CAMERA_TRACE(x) +#endif + +/* + * Function definitions + */ + +/*! + * csi ENC callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t csi_enc_callback(int irq, void *dev_id) +{ + cam_data *cam = (cam_data *) dev_id; + + if (cam->enc_callback == NULL) + return IRQ_HANDLED; + + cam->enc_callback(irq, dev_id); + return IRQ_HANDLED; +} + +/*! + * CSI ENC enable channel setup function + * + * @param cam struct cam_data * mxc capture instance + * + * @return status + */ +static int csi_enc_setup(cam_data *cam) +{ + ipu_channel_params_t params; + u32 pixel_fmt; + int err = 0; + dma_addr_t dummy = cam->dummy_frame.buffer.m.offset; + + CAMERA_TRACE("In csi_enc_setup\n"); + if (!cam) { + printk(KERN_ERR "cam private is NULL\n"); + return -ENXIO; + } + + memset(¶ms, 0, sizeof(ipu_channel_params_t)); + params.csi_mem.csi = cam->csi; + + if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + pixel_fmt = IPU_PIX_FMT_YUV420P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) + pixel_fmt = IPU_PIX_FMT_YUV422P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) + pixel_fmt = IPU_PIX_FMT_UYVY; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) + pixel_fmt = IPU_PIX_FMT_YUYV; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) + pixel_fmt = IPU_PIX_FMT_NV12; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24) + pixel_fmt = IPU_PIX_FMT_BGR24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) + pixel_fmt = IPU_PIX_FMT_RGB24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) + pixel_fmt = IPU_PIX_FMT_RGB565; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR32) + pixel_fmt = IPU_PIX_FMT_BGR32; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB32) + pixel_fmt = IPU_PIX_FMT_RGB32; + else { + printk(KERN_ERR "format not supported\n"); + return -EINVAL; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_ENC, cam->csi, true, true); + + err = ipu_init_channel(CSI_MEM, ¶ms); + if (err != 0) { + printk(KERN_ERR "ipu_init_channel %d\n", err); + return err; + } + + err = ipu_init_channel_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + pixel_fmt, cam->v2f.fmt.pix.width, + cam->v2f.fmt.pix.height, + cam->v2f.fmt.pix.width, IPU_ROTATE_NONE, + dummy, dummy, + cam->offset.u_offset, + cam->offset.v_offset); + if (err != 0) { + printk(KERN_ERR "CSI_MEM output buffer\n"); + return err; + } + err = ipu_enable_channel(CSI_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel CSI_MEM\n"); + return err; + } + + return err; +} + +/*! + * function to update physical buffer address for encorder IDMA channel + * + * @param eba physical buffer address for encorder IDMA channel + * @param buffer_num int buffer 0 or buffer 1 + * + * @return status + */ +static int csi_enc_eba_update(dma_addr_t eba, int *buffer_num) +{ + int err = 0; + + pr_debug("eba %x\n", eba); + err = ipu_update_channel_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + *buffer_num, eba); + if (err != 0) { + ipu_clear_buffer_ready(CSI_MEM, IPU_OUTPUT_BUFFER, + *buffer_num); + printk(KERN_ERR "err %d buffer_num %d\n", err, *buffer_num); + return err; + } + + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, *buffer_num); + + *buffer_num = (*buffer_num == 0) ? 1 : 0; + + return 0; +} + +/*! + * Enable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int csi_enc_enabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + CAMERA_TRACE("IPU:In csi_enc_enabling_tasks\n"); + + cam->dummy_frame.vaddress = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->dummy_frame.paddress, + GFP_DMA | GFP_KERNEL); + if (cam->dummy_frame.vaddress == 0) { + pr_err("ERROR: v4l2 capture: Allocate dummy frame " + "failed.\n"); + return -ENOBUFS; + } + cam->dummy_frame.buffer.type = V4L2_BUF_TYPE_PRIVATE; + cam->dummy_frame.buffer.length = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->dummy_frame.buffer.m.offset = cam->dummy_frame.paddress; + + ipu_clear_irq(IPU_IRQ_CSI0_OUT_EOF); + err = ipu_request_irq(IPU_IRQ_CSI0_OUT_EOF, + csi_enc_callback, 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR "Error registering rot irq\n"); + return err; + } + + err = csi_enc_setup(cam); + if (err != 0) { + printk(KERN_ERR "csi_enc_setup %d\n", err); + return err; + } + + return err; +} + +/*! + * Disable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +static int csi_enc_disabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + ipu_free_irq(IPU_IRQ_CSI0_OUT_EOF, cam); + + err = ipu_disable_channel(CSI_MEM, true); + + ipu_uninit_channel(CSI_MEM); + + if (cam->dummy_frame.vaddress != 0) { + dma_free_coherent(0, cam->dummy_frame.buffer.length, + cam->dummy_frame.vaddress, + cam->dummy_frame.paddress); + cam->dummy_frame.vaddress = 0; + } + ipu_csi_enable_mclk_if(CSI_MCLK_ENC, cam->csi, false, false); + + return err; +} + +/*! + * Enable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int csi_enc_enable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_enable_csi(cam->csi); +} + +/*! + * Disable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int csi_enc_disable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_disable_csi(cam->csi); +} + +/*! + * function to select CSI ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int csi_enc_select(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam) { + cam->enc_update_eba = csi_enc_eba_update; + cam->enc_enable = csi_enc_enabling_tasks; + cam->enc_disable = csi_enc_disabling_tasks; + cam->enc_enable_csi = csi_enc_enable_csi; + cam->enc_disable_csi = csi_enc_disable_csi; + } else { + err = -EIO; + } + + return err; +} + +/*! + * function to de-select CSI ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int csi_enc_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam) { + cam->enc_update_eba = NULL; + cam->enc_enable = NULL; + cam->enc_disable = NULL; + cam->enc_enable_csi = NULL; + cam->enc_disable_csi = NULL; + } + + return err; +} + +/*! + * Init the Encorder channels + * + * @return Error code indicating success or failure + */ +__init int csi_enc_init(void) +{ + return 0; +} + +/*! + * Deinit the Encorder channels + * + */ +void __exit csi_enc_exit(void) +{ +} + +module_init(csi_enc_init); +module_exit(csi_enc_exit); + +EXPORT_SYMBOL(csi_enc_select); +EXPORT_SYMBOL(csi_enc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CSI ENC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_enc.c b/drivers/media/video/mxc/capture/ipu_prp_enc.c new file mode 100644 index 000000000000..3a07bbfdcbaa --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_enc.c @@ -0,0 +1,491 @@ +/* + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_enc.c + * + * @brief IPU Use case for PRP-ENC + * + * @ingroup IPU + */ + +#include <linux/dma-mapping.h> +#include <linux/ipu.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +#ifdef CAMERA_DBG + #define CAMERA_TRACE(x) (printk)x +#else + #define CAMERA_TRACE(x) +#endif + +static ipu_rotate_mode_t grotation = IPU_ROTATE_NONE; + +/* + * Function definitions + */ + +/*! + * IPU ENC callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prp_enc_callback(int irq, void *dev_id) +{ + cam_data *cam = (cam_data *) dev_id; + + if (cam->enc_callback == NULL) + return IRQ_HANDLED; + + cam->enc_callback(irq, dev_id); + + return IRQ_HANDLED; +} + +/*! + * PrpENC enable channel setup function + * + * @param cam struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_enc_setup(cam_data * cam) +{ + ipu_channel_params_t enc; + int err = 0; + dma_addr_t dummy = 0xdeadbeaf; + + CAMERA_TRACE("In prp_enc_setup\n"); + if (!cam) { + printk(KERN_ERR "cam private is NULL\n"); + return -ENXIO; + } + memset(&enc, 0, sizeof(ipu_channel_params_t)); + + ipu_csi_get_window_size(&enc.csi_prp_enc_mem.in_width, + &enc.csi_prp_enc_mem.in_height, cam->csi); + + enc.csi_prp_enc_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; + enc.csi_prp_enc_mem.out_width = cam->v2f.fmt.pix.width; + enc.csi_prp_enc_mem.out_height = cam->v2f.fmt.pix.height; + enc.csi_prp_enc_mem.csi = cam->csi; + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + enc.csi_prp_enc_mem.out_width = cam->v2f.fmt.pix.height; + enc.csi_prp_enc_mem.out_height = cam->v2f.fmt.pix.width; + } + + if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_YUV420P; + pr_info("YUV420\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_YUV422P; + pr_info("YUV422P\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_YUYV; + pr_info("YUYV\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_UYVY; + pr_info("UYVY\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_NV12; + pr_info("NV12\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_BGR24; + pr_info("BGR24\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_RGB24; + pr_info("RGB24\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_RGB565; + pr_info("RGB565\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR32) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_BGR32; + pr_info("BGR32\n"); + } else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB32) { + enc.csi_prp_enc_mem.out_pixel_fmt = IPU_PIX_FMT_RGB32; + pr_info("RGB32\n"); + } else { + printk(KERN_ERR "format not supported\n"); + return -EINVAL; + } + + err = ipu_init_channel(CSI_PRP_ENC_MEM, &enc); + if (err != 0) { + printk(KERN_ERR "ipu_init_channel %d\n", err); + return err; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_ENC, cam->csi, true, true); + + grotation = cam->rotation; + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + if (cam->rot_enc_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_enc_buf_size[0], + cam->rot_enc_bufs_vaddr[0], + cam->rot_enc_bufs[0]); + } + if (cam->rot_enc_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_enc_buf_size[1], + cam->rot_enc_bufs_vaddr[1], + cam->rot_enc_bufs[1]); + } + cam->rot_enc_buf_size[0] = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->rot_enc_bufs_vaddr[0] = + (void *)dma_alloc_coherent(0, cam->rot_enc_buf_size[0], + &cam->rot_enc_bufs[0], + GFP_DMA | GFP_KERNEL); + if (!cam->rot_enc_bufs_vaddr[0]) { + printk(KERN_ERR "alloc enc_bufs0\n"); + return -ENOMEM; + } + cam->rot_enc_buf_size[1] = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->rot_enc_bufs_vaddr[1] = + (void *)dma_alloc_coherent(0, cam->rot_enc_buf_size[1], + &cam->rot_enc_bufs[1], + GFP_DMA | GFP_KERNEL); + if (!cam->rot_enc_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_enc_buf_size[0], + cam->rot_enc_bufs_vaddr[0], + cam->rot_enc_bufs[0]); + cam->rot_enc_bufs_vaddr[0] = NULL; + cam->rot_enc_bufs[0] = 0; + printk(KERN_ERR "alloc enc_bufs1\n"); + return -ENOMEM; + } + + err = ipu_init_channel_buffer(CSI_PRP_ENC_MEM, + IPU_OUTPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_width, + enc.csi_prp_enc_mem.out_height, + enc.csi_prp_enc_mem.out_width, + IPU_ROTATE_NONE, + cam->rot_enc_bufs[0], + cam->rot_enc_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "CSI_PRP_ENC_MEM err\n"); + return err; + } + + err = ipu_init_channel(MEM_ROT_ENC_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "MEM_ROT_ENC_MEM channel err\n"); + return err; + } + + err = ipu_init_channel_buffer(MEM_ROT_ENC_MEM, IPU_INPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_width, + enc.csi_prp_enc_mem.out_height, + enc.csi_prp_enc_mem.out_width, + cam->rotation, + cam->rot_enc_bufs[0], + cam->rot_enc_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "MEM_ROT_ENC_MEM input buffer\n"); + return err; + } + + err = + ipu_init_channel_buffer(MEM_ROT_ENC_MEM, IPU_OUTPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_height, + enc.csi_prp_enc_mem.out_width, + cam->v2f.fmt.pix.bytesperline / + bytes_per_pixel(enc.csi_prp_enc_mem. + out_pixel_fmt), + IPU_ROTATE_NONE, dummy, dummy, + cam->offset.u_offset, + cam->offset.v_offset); + if (err != 0) { + printk(KERN_ERR "MEM_ROT_ENC_MEM output buffer\n"); + return err; + } + + err = ipu_link_channels(CSI_PRP_ENC_MEM, MEM_ROT_ENC_MEM); + if (err < 0) { + printk(KERN_ERR + "link CSI_PRP_ENC_MEM-MEM_ROT_ENC_MEM\n"); + return err; + } + + err = ipu_enable_channel(CSI_PRP_ENC_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel CSI_PRP_ENC_MEM\n"); + return err; + } + err = ipu_enable_channel(MEM_ROT_ENC_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel MEM_ROT_ENC_MEM\n"); + return err; + } + + ipu_select_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, 1); + } else { + err = + ipu_init_channel_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, + enc.csi_prp_enc_mem.out_pixel_fmt, + enc.csi_prp_enc_mem.out_width, + enc.csi_prp_enc_mem.out_height, + cam->v2f.fmt.pix.bytesperline / + bytes_per_pixel(enc.csi_prp_enc_mem. + out_pixel_fmt), + cam->rotation, dummy, dummy, + cam->offset.u_offset, + cam->offset.v_offset); + if (err != 0) { + printk(KERN_ERR "CSI_PRP_ENC_MEM output buffer\n"); + return err; + } + err = ipu_enable_channel(CSI_PRP_ENC_MEM); + if (err < 0) { + printk(KERN_ERR "ipu_enable_channel CSI_PRP_ENC_MEM\n"); + return err; + } + } + + return err; +} + +/*! + * function to update physical buffer address for encorder IDMA channel + * + * @param eba physical buffer address for encorder IDMA channel + * @param buffer_num int buffer 0 or buffer 1 + * + * @return status + */ +static int prp_enc_eba_update(dma_addr_t eba, int *buffer_num) +{ + int err = 0; + + pr_debug("eba %x\n", eba); + if (grotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_update_channel_buffer(MEM_ROT_ENC_MEM, + IPU_OUTPUT_BUFFER, *buffer_num, + eba); + } else { + err = ipu_update_channel_buffer(CSI_PRP_ENC_MEM, + IPU_OUTPUT_BUFFER, *buffer_num, + eba); + } + if (err != 0) { + printk(KERN_ERR "err %d buffer_num %d\n", err, *buffer_num); + return err; + } + + if (grotation >= IPU_ROTATE_90_RIGHT) { + ipu_select_buffer(MEM_ROT_ENC_MEM, IPU_OUTPUT_BUFFER, + *buffer_num); + } else { + ipu_select_buffer(CSI_PRP_ENC_MEM, IPU_OUTPUT_BUFFER, + *buffer_num); + } + + *buffer_num = (*buffer_num == 0) ? 1 : 0; + return 0; +} + +/*! + * Enable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_enc_enabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + CAMERA_TRACE("IPU:In prp_enc_enabling_tasks\n"); + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_request_irq(IPU_IRQ_PRP_ENC_ROT_OUT_EOF, + prp_enc_callback, 0, "Mxc Camera", cam); + } else { + err = ipu_request_irq(IPU_IRQ_PRP_ENC_OUT_EOF, + prp_enc_callback, 0, "Mxc Camera", cam); + } + if (err != 0) { + printk(KERN_ERR "Error registering rot irq\n"); + return err; + } + + err = prp_enc_setup(cam); + if (err != 0) { + printk(KERN_ERR "prp_enc_setup %d\n", err); + return err; + } + + return err; +} + +/*! + * Disable encoder task + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +static int prp_enc_disabling_tasks(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_free_irq(IPU_IRQ_PRP_ENC_ROT_OUT_EOF, cam); + } else { + ipu_free_irq(IPU_IRQ_PRP_ENC_OUT_EOF, cam); + } + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_unlink_channels(CSI_PRP_ENC_MEM, MEM_ROT_ENC_MEM); + } + + err = ipu_disable_channel(CSI_PRP_ENC_MEM, true); + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + err |= ipu_disable_channel(MEM_ROT_ENC_MEM, true); + } + + ipu_uninit_channel(CSI_PRP_ENC_MEM); + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + ipu_uninit_channel(MEM_ROT_ENC_MEM); + } + + ipu_csi_enable_mclk_if(CSI_MCLK_ENC, cam->csi, false, false); + + return err; +} + +/*! + * Enable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_enc_enable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_enable_csi(cam->csi); +} + +/*! + * Disable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_enc_disable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_disable_csi(cam->csi); +} + +/*! + * function to select PRP-ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int prp_enc_select(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam) { + cam->enc_update_eba = prp_enc_eba_update; + cam->enc_enable = prp_enc_enabling_tasks; + cam->enc_disable = prp_enc_disabling_tasks; + cam->enc_enable_csi = prp_enc_enable_csi; + cam->enc_disable_csi = prp_enc_disable_csi; + } else { + err = -EIO; + } + + return err; +} + +/*! + * function to de-select PRP-ENC as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return int + */ +int prp_enc_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + //err = prp_enc_disabling_tasks(cam); + + if (cam) { + cam->enc_update_eba = NULL; + cam->enc_enable = NULL; + cam->enc_disable = NULL; + cam->enc_enable_csi = NULL; + cam->enc_disable_csi = NULL; + if (cam->rot_enc_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_enc_buf_size[0], + cam->rot_enc_bufs_vaddr[0], + cam->rot_enc_bufs[0]); + cam->rot_enc_bufs_vaddr[0] = NULL; + cam->rot_enc_bufs[0] = 0; + } + if (cam->rot_enc_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_enc_buf_size[1], + cam->rot_enc_bufs_vaddr[1], + cam->rot_enc_bufs[1]); + cam->rot_enc_bufs_vaddr[1] = NULL; + cam->rot_enc_bufs[1] = 0; + } + } + + return err; +} + +/*! + * Init the Encorder channels + * + * @return Error code indicating success or failure + */ +__init int prp_enc_init(void) +{ + return 0; +} + +/*! + * Deinit the Encorder channels + * + */ +void __exit prp_enc_exit(void) +{ +} + +module_init(prp_enc_init); +module_exit(prp_enc_exit); + +EXPORT_SYMBOL(prp_enc_select); +EXPORT_SYMBOL(prp_enc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP ENC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_sw.h b/drivers/media/video/mxc/capture/ipu_prp_sw.h new file mode 100644 index 000000000000..0d51e2ae8670 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_sw.h @@ -0,0 +1,38 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_sw.h + * + * @brief This file contains the IPU PRP use case driver header. + * + * @ingroup IPU + */ + +#ifndef _INCLUDE_IPU__PRP_SW_H_ +#define _INCLUDE_IPU__PRP_SW_H_ + +int csi_enc_select(void *private); +int csi_enc_deselect(void *private); +int prp_enc_select(void *private); +int prp_enc_deselect(void *private); +int prp_vf_adc_select(void *private); +int prp_vf_sdc_select(void *private); +int prp_vf_sdc_select_bg(void *private); +int prp_vf_adc_deselect(void *private); +int prp_vf_sdc_deselect(void *private); +int prp_vf_sdc_deselect_bg(void *private); +int prp_still_select(void *private); +int prp_still_deselect(void *private); + +#endif diff --git a/drivers/media/video/mxc/capture/ipu_prp_vf_adc.c b/drivers/media/video/mxc/capture/ipu_prp_vf_adc.c new file mode 100644 index 000000000000..a7ac09ae87d4 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_vf_adc.c @@ -0,0 +1,601 @@ +/* + * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_vf_adc.c + * + * @brief IPU Use case for PRP-VF + * + * @ingroup IPU + */ + +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" +#include <mach/mxcfb.h> +#include <mach/ipu.h> +#include <linux/dma-mapping.h> + +/* + * Function definitions + */ + +/*! + * prpvf_start - start the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_start(void *private) +{ + cam_data *cam = (cam_data *) private; + ipu_channel_params_t vf; + ipu_channel_params_t params; + u32 format = IPU_PIX_FMT_RGB565; + u32 size = 2; + int err = 0; + + if (!cam) { + printk(KERN_ERR "prpvf_start private is NULL\n"); + return -ENXIO; + } + + if (cam->overlay_active == true) { + printk(KERN_ERR "prpvf_start already start.\n"); + return 0; + } + + mxcfb_set_refresh_mode(cam->overlay_fb, MXCFB_REFRESH_OFF, 0); + + memset(&vf, 0, sizeof(ipu_channel_params_t)); + ipu_csi_get_window_size(&vf.csi_prp_vf_adc.in_width, + &vf.csi_prp_vf_adc.in_height); + vf.csi_prp_vf_adc.in_pixel_fmt = IPU_PIX_FMT_UYVY; + vf.csi_prp_vf_adc.out_width = cam->win.w.width; + vf.csi_prp_vf_adc.out_height = cam->win.w.height; + vf.csi_prp_vf_adc.graphics_combine_en = 0; + vf.csi_prp_vf_adc.out_left = cam->win.w.left; + + /* hope to be removed when those offset taken cared by adc driver. */ +#ifdef CONFIG_FB_MXC_EPSON_QVGA_PANEL + vf.csi_prp_vf_adc.out_left += 12; +#endif +#ifdef CONFIG_FB_MXC_EPSON_PANEL + vf.csi_prp_vf_adc.out_left += 2; +#endif + + vf.csi_prp_vf_adc.out_top = cam->win.w.top; + + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + vf.csi_prp_vf_adc.out_width = cam->win.w.height; + vf.csi_prp_vf_adc.out_height = cam->win.w.width; + + size = cam->win.w.width * cam->win.w.height * size; + vf.csi_prp_vf_adc.out_pixel_fmt = format; + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) + return err; + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, true, true); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + cam->vf_bufs[0]); + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = size; + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam-> + vf_bufs_size + [0], + &cam-> + vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + cam->vf_bufs_size[1] = size; + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam-> + vf_bufs_size + [1], + &cam-> + vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, + cam->vf_bufs[0], cam->vf_bufs[1], + 0, 0); + if (err != 0) + goto out_3; + + if (cam->rot_vf_bufs[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + } + if (cam->rot_vf_bufs[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + } + cam->rot_vf_buf_size[0] = PAGE_ALIGN(size); + cam->rot_vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam-> + rot_vf_buf_size + [0], + &cam-> + rot_vf_bufs + [0], + GFP_DMA | + GFP_KERNEL); + if (cam->rot_vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate rot_vf_bufs\n"); + err = -ENOMEM; + goto out_3; + } + cam->rot_vf_buf_size[1] = PAGE_ALIGN(size); + cam->rot_vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam-> + rot_vf_buf_size + [1], + &cam-> + rot_vf_bufs + [1], + GFP_DMA | + GFP_KERNEL); + if (cam->rot_vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate rot_vf_bufs\n"); + err = -ENOMEM; + goto out_3; + } + err = ipu_init_channel(MEM_ROT_VF_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "prpvf_start :Error " + "MEM_ROT_VF_MEM channel\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->vf_rotation, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "MEM_ROT_VF_MEM input buffer\n"); + goto out_2; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + IPU_ROTATE_NONE, + cam->rot_vf_bufs[0], + cam->rot_vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + if (err < 0) { + printk(KERN_ERR "prpvf_start: Error " + "linking CSI_PRP_VF_MEM-MEM_ROT_VF_MEM\n"); + goto out_2; + } + + ipu_disable_channel(ADC_SYS2, false); + ipu_uninit_channel(ADC_SYS2); + + params.adc_sys2.disp = DISP0; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = cam->win.w.left; + /* going to be removed when those offset taken cared by adc driver. */ +#ifdef CONFIG_FB_MXC_EPSON_QVGA_PANEL + params.adc_sys2.out_left += 12; +#endif +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.adc_sys2.out_left += 2; +#endif + params.adc_sys2.out_top = cam->win.w.top; + err = ipu_init_channel(ADC_SYS2, ¶ms); + if (err != 0) { + printk(KERN_ERR + "prpvf_start: Error initializing ADC SYS1\n"); + goto out_2; + } + + err = ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + IPU_ROTATE_NONE, + cam->rot_vf_bufs[0], + cam->rot_vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing ADC SYS1 buffer\n"); + goto out_1; + } + + err = ipu_link_channels(MEM_ROT_VF_MEM, ADC_SYS2); + if (err < 0) { + printk(KERN_ERR + "Error linking MEM_ROT_VF_MEM-ADC_SYS2\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(MEM_ROT_VF_MEM); + ipu_enable_channel(ADC_SYS2); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } +#ifndef CONFIG_MXC_IPU_PRP_VF_SDC + else if (cam->vf_rotation == IPU_ROTATE_NONE) { + vf.csi_prp_vf_adc.out_pixel_fmt = IPU_PIX_FMT_BGR32; + err = ipu_init_channel(CSI_PRP_VF_ADC, &vf); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing CSI_PRP_VF_ADC\n"); + return err; + } + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, true, true); + err = ipu_init_channel_buffer(CSI_PRP_VF_ADC, IPU_OUTPUT_BUFFER, + format, cam->win.w.width, + cam->win.w.height, + cam->win.w.width, IPU_ROTATE_NONE, + 0, 0, 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing CSI_PRP_VF_MEM\n"); + return err; + } + ipu_enable_channel(CSI_PRP_VF_ADC); + } +#endif + else { + size = cam->win.w.width * cam->win.w.height * size; + vf.csi_prp_vf_adc.out_pixel_fmt = format; + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing CSI_PRP_VF_MEM\n"); + return err; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, true, true); + + if (cam->vf_bufs[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + cam->vf_bufs[0]); + } + if (cam->vf_bufs[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam-> + vf_bufs_size + [0], + &cam-> + vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate vf_bufs\n"); + err = -ENOMEM; + goto out_3; + } + cam->vf_bufs_size[1] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam-> + vf_bufs_size + [1], + &cam-> + vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR + "prpvf_start: Error to allocate vf_bufs\n"); + err = -ENOMEM; + goto out_3; + } + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->vf_rotation, + cam->vf_bufs[0], cam->vf_bufs[1], + 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing CSI_PRP_VF_MEM\n"); + goto out_3; + } + + ipu_disable_channel(ADC_SYS2, false); + ipu_uninit_channel(ADC_SYS2); + + params.adc_sys2.disp = DISP0; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; + params.adc_sys2.out_left = cam->win.w.left; + // going to be removed when those offset taken cared by adc driver. +#ifdef CONFIG_FB_MXC_EPSON_QVGA_PANEL + params.adc_sys2.out_left += 12; +#endif +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.adc_sys2.out_left += 2; +#endif + params.adc_sys2.out_top = cam->win.w.top; + err = ipu_init_channel(ADC_SYS2, ¶ms); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing ADC_SYS2\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(ADC_SYS2, IPU_INPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "prpvf_start: Error " + "initializing ADC SYS1 buffer\n"); + goto out_1; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, ADC_SYS2); + if (err < 0) { + printk(KERN_ERR "prpvf_start: Error " + "linking MEM_ROT_VF_MEM-ADC_SYS2\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(ADC_SYS2); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } + + cam->overlay_active = true; + return err; + + out_1: + ipu_uninit_channel(ADC_SYS2); + out_2: + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + ipu_uninit_channel(MEM_ROT_VF_MEM); + } + out_3: + ipu_uninit_channel(CSI_PRP_VF_MEM); + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + return err; +} + +/*! + * prpvf_stop - stop the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + if (cam->overlay_active == false) + return 0; + + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + ipu_unlink_channels(MEM_ROT_VF_MEM, ADC_SYS2); + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + ipu_disable_channel(MEM_ROT_VF_MEM, true); + ipu_disable_channel(ADC_SYS2, true); + + ipu_uninit_channel(CSI_PRP_VF_MEM); + ipu_uninit_channel(MEM_ROT_VF_MEM); + ipu_uninit_channel(ADC_SYS2); + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, false, false); + } +#ifndef CONFIG_MXC_IPU_PRP_VF_SDC + else if (cam->vf_rotation == IPU_ROTATE_NONE) { + ipu_disable_channel(CSI_PRP_VF_ADC, false); + ipu_uninit_channel(CSI_PRP_VF_ADC); + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, false, false); + } +#endif + else { + ipu_unlink_channels(CSI_PRP_VF_MEM, ADC_SYS2); + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + ipu_disable_channel(ADC_SYS2, true); + + ipu_uninit_channel(CSI_PRP_VF_MEM); + ipu_uninit_channel(ADC_SYS2); + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, false, false); + } + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + + cam->overlay_active = false; + + mxcfb_set_refresh_mode(cam->overlay_fb, MXCFB_REFRESH_PARTIAL, 0); + return err; +} + +/*! + * function to select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_adc_select(void *private) +{ + cam_data *cam; + if (private) { + cam = (cam_data *) private; + cam->vf_start_adc = prpvf_start; + cam->vf_stop_adc = prpvf_stop; + cam->overlay_active = false; + } else { + return -EIO; + } + return 0; +} + +/*! + * function to de-select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_adc_deselect(void *private) +{ + cam_data *cam; + int err = 0; + err = prpvf_stop(private); + + if (private) { + cam = (cam_data *) private; + cam->vf_start_adc = NULL; + cam->vf_stop_adc = NULL; + } + return err; +} + +/*! + * Init viewfinder task. + * + * @return Error code indicating success or failure + */ +__init int prp_vf_adc_init(void) +{ + return 0; +} + +/*! + * Deinit viewfinder task. + * + * @return Error code indicating success or failure + */ +void __exit prp_vf_adc_exit(void) +{ +} + +module_init(prp_vf_adc_init); +module_exit(prp_vf_adc_exit); + +EXPORT_SYMBOL(prp_vf_adc_select); +EXPORT_SYMBOL(prp_vf_adc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP VF ADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_vf_sdc.c b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc.c new file mode 100644 index 000000000000..882bacf923b1 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc.c @@ -0,0 +1,467 @@ +/* + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_vf_sdc.c + * + * @brief IPU Use case for PRP-VF + * + * @ingroup IPU + */ + +#include <linux/dma-mapping.h> +#include <linux/console.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <mach/hardware.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +#define OVERLAY_FB_SUPPORT_NONSTD (cpu_is_mx51_rev(CHIP_REV_2_0) >= 1) + +/* + * Function definitions + */ + +/*! + * prpvf_start - start the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_start(void *private) +{ + struct fb_var_screeninfo fbvar; + struct fb_info *fbi = NULL; + cam_data *cam = (cam_data *) private; + ipu_channel_params_t vf; + u32 vf_out_format = 0; + u32 size = 2, temp = 0; + int err = 0, i = 0; + + if (!cam) { + printk(KERN_ERR "private is NULL\n"); + return -EIO; + } + + if (cam->overlay_active == true) { + pr_debug("already started.\n"); + return 0; + } + + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strcmp(idstr, "DISP3 FG") == 0) { + fbi = registered_fb[i]; + break; + } + } + + if (fbi == NULL) { + printk(KERN_ERR "DISP3 FG fb not found\n"); + return -EPERM; + } + + fbvar = fbi->var; + + /* Store the overlay frame buffer's original std */ + cam->fb_origin_std = fbvar.nonstd; + + if (OVERLAY_FB_SUPPORT_NONSTD) { + /* Use DP to do CSC so that we can get better performance */ + vf_out_format = IPU_PIX_FMT_UYVY; + fbvar.nonstd = vf_out_format; + } else { + vf_out_format = IPU_PIX_FMT_RGB565; + fbvar.nonstd = 0; + } + + fbvar.bits_per_pixel = 16; + fbvar.xres = fbvar.xres_virtual = cam->win.w.width; + fbvar.yres = cam->win.w.height; + fbvar.yres_virtual = cam->win.w.height * 2; + fbvar.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbvar); + + ipu_disp_set_window_pos(MEM_FG_SYNC, cam->win.w.left, + cam->win.w.top); + + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_UNBLANK); + release_console_sem(); + + memset(&vf, 0, sizeof(ipu_channel_params_t)); + ipu_csi_get_window_size(&vf.csi_prp_vf_mem.in_width, + &vf.csi_prp_vf_mem.in_height, cam->csi); + vf.csi_prp_vf_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; + vf.csi_prp_vf_mem.out_width = cam->win.w.width; + vf.csi_prp_vf_mem.out_height = cam->win.w.height; + vf.csi_prp_vf_mem.csi = cam->csi; + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + vf.csi_prp_vf_mem.out_width = cam->win.w.height; + vf.csi_prp_vf_mem.out_height = cam->win.w.width; + } + vf.csi_prp_vf_mem.out_pixel_fmt = vf_out_format; + size = cam->win.w.width * cam->win.w.height * size; + + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) + goto out_5; + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, true, true); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + (dma_addr_t) cam->vf_bufs[0]); + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + (dma_addr_t) cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[0], + (dma_addr_t *) & + cam->vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_4; + } + cam->vf_bufs_size[1] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[1], + (dma_addr_t *) & + cam->vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + pr_debug("vf_bufs %x %x\n", cam->vf_bufs[0], cam->vf_bufs[1]); + + if (cam->vf_rotation >= IPU_ROTATE_VERT_FLIP) { + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + vf_out_format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + goto out_3; + } + + err = ipu_init_channel(MEM_ROT_VF_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM channel\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, + vf_out_format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->vf_rotation, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM input buffer\n"); + goto out_2; + } + + if (cam->vf_rotation < IPU_ROTATE_90_RIGHT) { + temp = vf.csi_prp_vf_mem.out_width; + vf.csi_prp_vf_mem.out_width = + vf.csi_prp_vf_mem.out_height; + vf.csi_prp_vf_mem.out_height = temp; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + vf_out_format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + IPU_ROTATE_NONE, + fbi->fix.smem_start + + (fbi->fix.line_length * + fbi->var.yres), + fbi->fix.smem_start, 0, 0); + + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + if (err < 0) { + printk(KERN_ERR + "Error link CSI_PRP_VF_MEM-MEM_ROT_VF_MEM\n"); + goto out_2; + } + + err = ipu_link_channels(MEM_ROT_VF_MEM, MEM_FG_SYNC); + if (err < 0) { + printk(KERN_ERR + "Error link MEM_ROT_VF_MEM-MEM_FG_SYNC\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(MEM_ROT_VF_MEM); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } else { + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + vf_out_format, cam->win.w.width, + cam->win.w.height, + cam->win.w.width, + cam->vf_rotation, + fbi->fix.smem_start + + (fbi->fix.line_length * + fbi->var.yres), + fbi->fix.smem_start, 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing CSI_PRP_VF_MEM\n"); + goto out_4; + } + + err = ipu_link_channels(CSI_PRP_VF_MEM, MEM_FG_SYNC); + if (err < 0) { + printk(KERN_ERR "Error linking ipu channels\n"); + goto out_4; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } + + cam->overlay_active = true; + return err; + +out_1: + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); +out_2: + if (cam->vf_rotation >= IPU_ROTATE_VERT_FLIP) { + ipu_uninit_channel(MEM_ROT_VF_MEM); + } +out_3: + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + (dma_addr_t) cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + (dma_addr_t) cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } +out_4: + ipu_uninit_channel(CSI_PRP_VF_MEM); +out_5: + return err; +} + +/*! + * prpvf_stop - stop the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0, i = 0; + struct fb_info *fbi = NULL; + struct fb_var_screeninfo fbvar; + + if (cam->overlay_active == false) + return 0; + + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strcmp(idstr, "DISP3 FG") == 0) { + fbi = registered_fb[i]; + break; + } + } + + if (fbi == NULL) { + printk(KERN_ERR "DISP3 FG fb not found\n"); + return -EPERM; + } + + ipu_disp_set_window_pos(MEM_FG_SYNC, 0, 0); + + if (cam->vf_rotation >= IPU_ROTATE_VERT_FLIP) { + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_ROT_VF_MEM); + ipu_unlink_channels(MEM_ROT_VF_MEM, MEM_FG_SYNC); + } else { + ipu_unlink_channels(CSI_PRP_VF_MEM, MEM_FG_SYNC); + } + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + + if (cam->vf_rotation >= IPU_ROTATE_VERT_FLIP) { + ipu_disable_channel(MEM_ROT_VF_MEM, true); + ipu_uninit_channel(MEM_ROT_VF_MEM); + } + ipu_uninit_channel(CSI_PRP_VF_MEM); + + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + release_console_sem(); + + /* Set the overlay frame buffer std to what it is used to be */ + fbvar = fbi->var; + fbvar.nonstd = cam->fb_origin_std; + fbvar.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbvar); + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, false, false); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], + (dma_addr_t) cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], + (dma_addr_t) cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + + cam->overlay_active = false; + return err; +} + +/*! + * Enable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_vf_enable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_enable_csi(cam->csi); +} + +/*! + * Disable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_vf_disable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_disable_csi(cam->csi); +} + +/*! + * function to select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_sdc_select(void *private) +{ + cam_data *cam; + int err = 0; + if (private) { + cam = (cam_data *) private; + cam->vf_start_sdc = prpvf_start; + cam->vf_stop_sdc = prpvf_stop; + cam->vf_enable_csi = prp_vf_enable_csi; + cam->vf_disable_csi = prp_vf_disable_csi; + cam->overlay_active = false; + } else + err = -EIO; + + return err; +} + +/*! + * function to de-select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return int + */ +int prp_vf_sdc_deselect(void *private) +{ + cam_data *cam; + int err = 0; + err = prpvf_stop(private); + + if (private) { + cam = (cam_data *) private; + cam->vf_start_sdc = NULL; + cam->vf_stop_sdc = NULL; + cam->vf_enable_csi = NULL; + cam->vf_disable_csi = NULL; + } + return err; +} + +/*! + * Init viewfinder task. + * + * @return Error code indicating success or failure + */ +__init int prp_vf_sdc_init(void) +{ + return 0; +} + +/*! + * Deinit viewfinder task. + * + * @return Error code indicating success or failure + */ +void __exit prp_vf_sdc_exit(void) +{ +} + +module_init(prp_vf_sdc_init); +module_exit(prp_vf_sdc_exit); + +EXPORT_SYMBOL(prp_vf_sdc_select); +EXPORT_SYMBOL(prp_vf_sdc_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP VF SDC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_prp_vf_sdc_bg.c b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc_bg.c new file mode 100644 index 000000000000..7f0984c42950 --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_prp_vf_sdc_bg.c @@ -0,0 +1,443 @@ +/* + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_prp_vf_sdc_bg.c + * + * @brief IPU Use case for PRP-VF back-ground + * + * @ingroup IPU + */ +#include <linux/dma-mapping.h> +#include <linux/fb.h> +#include <linux/ipu.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +static int buffer_num = 0; +static int buffer_ready = 0; + +/* + * Function definitions + */ + +/*! + * SDC V-Sync callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prpvf_sdc_vsync_callback(int irq, void *dev_id) +{ + pr_debug("buffer_ready %d buffer_num %d\n", buffer_ready, buffer_num); + if (buffer_ready > 0) { + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + buffer_ready--; + } + + return IRQ_HANDLED; +} + +/*! + * VF EOF callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prpvf_vf_eof_callback(int irq, void *dev_id) +{ + pr_debug("buffer_ready %d buffer_num %d\n", buffer_ready, buffer_num); + + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, buffer_num); + + buffer_num = (buffer_num == 0) ? 1 : 0; + + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, buffer_num); + buffer_ready++; + return IRQ_HANDLED; +} + +/*! + * prpvf_start - start the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_start(void *private) +{ + cam_data *cam = (cam_data *) private; + ipu_channel_params_t vf; + u32 format; + u32 offset; + u32 bpp, size = 3; + int err = 0; + + if (!cam) { + printk(KERN_ERR "private is NULL\n"); + return -EIO; + } + + if (cam->overlay_active == true) { + pr_debug("already start.\n"); + return 0; + } + + format = cam->v4l2_fb.fmt.pixelformat; + if (cam->v4l2_fb.fmt.pixelformat == IPU_PIX_FMT_BGR24) { + bpp = 3, size = 3; + pr_info("BGR24\n"); + } else if (cam->v4l2_fb.fmt.pixelformat == IPU_PIX_FMT_RGB565) { + bpp = 2, size = 2; + pr_info("RGB565\n"); + } else if (cam->v4l2_fb.fmt.pixelformat == IPU_PIX_FMT_BGR32) { + bpp = 4, size = 4; + pr_info("BGR32\n"); + } else { + printk(KERN_ERR + "unsupported fix format from the framebuffer.\n"); + return -EINVAL; + } + + offset = cam->v4l2_fb.fmt.bytesperline * cam->win.w.top + + size * cam->win.w.left; + + if (cam->v4l2_fb.base == 0) { + printk(KERN_ERR "invalid frame buffer address.\n"); + } else { + offset += (u32) cam->v4l2_fb.base; + } + + memset(&vf, 0, sizeof(ipu_channel_params_t)); + ipu_csi_get_window_size(&vf.csi_prp_vf_mem.in_width, + &vf.csi_prp_vf_mem.in_height, cam->csi); + vf.csi_prp_vf_mem.in_pixel_fmt = IPU_PIX_FMT_UYVY; + vf.csi_prp_vf_mem.out_width = cam->win.w.width; + vf.csi_prp_vf_mem.out_height = cam->win.w.height; + vf.csi_prp_vf_mem.csi = cam->csi; + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + vf.csi_prp_vf_mem.out_width = cam->win.w.height; + vf.csi_prp_vf_mem.out_height = cam->win.w.width; + } + vf.csi_prp_vf_mem.out_pixel_fmt = format; + size = cam->win.w.width * cam->win.w.height * size; + + err = ipu_init_channel(CSI_PRP_VF_MEM, &vf); + if (err != 0) + goto out_4; + + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, true, true); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + } + cam->vf_bufs_size[0] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[0] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[0], + &cam->vf_bufs[0], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[0] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + cam->vf_bufs_size[1] = PAGE_ALIGN(size); + cam->vf_bufs_vaddr[1] = (void *)dma_alloc_coherent(0, + cam->vf_bufs_size[1], + &cam->vf_bufs[1], + GFP_DMA | + GFP_KERNEL); + if (cam->vf_bufs_vaddr[1] == NULL) { + printk(KERN_ERR "Error to allocate vf buffer\n"); + err = -ENOMEM; + goto out_3; + } + + err = ipu_init_channel_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, + format, vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + IPU_ROTATE_NONE, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error initializing CSI_PRP_VF_MEM\n"); + goto out_3; + } + err = ipu_init_channel(MEM_ROT_VF_MEM, NULL); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM channel\n"); + goto out_3; + } + + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_INPUT_BUFFER, + format, vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->vf_rotation, cam->vf_bufs[0], + cam->vf_bufs[1], 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM input buffer\n"); + goto out_2; + } + + if (cam->vf_rotation >= IPU_ROTATE_90_RIGHT) { + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_height, + vf.csi_prp_vf_mem.out_width, + cam->overlay_fb->var.xres * bpp, + IPU_ROTATE_NONE, offset, 0, 0, 0); + + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + } else { + err = ipu_init_channel_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, + format, + vf.csi_prp_vf_mem.out_width, + vf.csi_prp_vf_mem.out_height, + cam->overlay_fb->var.xres * bpp, + IPU_ROTATE_NONE, offset, 0, 0, 0); + if (err != 0) { + printk(KERN_ERR "Error MEM_ROT_VF_MEM output buffer\n"); + goto out_2; + } + } + + ipu_clear_irq(IPU_IRQ_PRP_VF_OUT_EOF); + err = ipu_request_irq(IPU_IRQ_PRP_VF_OUT_EOF, prpvf_vf_eof_callback, + 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR + "Error registering IPU_IRQ_PRP_VF_OUT_EOF irq.\n"); + goto out_2; + } + + ipu_clear_irq(IPU_IRQ_BG_SF_END); + err = ipu_request_irq(IPU_IRQ_BG_SF_END, prpvf_sdc_vsync_callback, + 0, "Mxc Camera", NULL); + if (err != 0) { + printk(KERN_ERR "Error registering IPU_IRQ_BG_SF_END irq.\n"); + goto out_1; + } + + ipu_enable_channel(CSI_PRP_VF_MEM); + ipu_enable_channel(MEM_ROT_VF_MEM); + + buffer_num = 0; + buffer_ready = 0; + ipu_select_buffer(CSI_PRP_VF_MEM, IPU_OUTPUT_BUFFER, 0); + + cam->overlay_active = true; + return err; + + out_1: + ipu_free_irq(IPU_IRQ_PRP_VF_OUT_EOF, NULL); + out_2: + ipu_uninit_channel(MEM_ROT_VF_MEM); + out_3: + ipu_uninit_channel(CSI_PRP_VF_MEM); + out_4: + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + return err; +} + +/*! + * prpvf_stop - stop the vf task + * + * @param private cam_data * mxc v4l2 main structure + * + */ +static int prpvf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam->overlay_active == false) + return 0; + + ipu_free_irq(IPU_IRQ_BG_SF_END, NULL); + + ipu_free_irq(IPU_IRQ_PRP_VF_OUT_EOF, cam); + + ipu_disable_channel(CSI_PRP_VF_MEM, true); + ipu_disable_channel(MEM_ROT_VF_MEM, true); + ipu_uninit_channel(CSI_PRP_VF_MEM); + ipu_uninit_channel(MEM_ROT_VF_MEM); + ipu_csi_enable_mclk_if(CSI_MCLK_VF, cam->csi, false, false); + + if (cam->vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->vf_bufs_size[0], + cam->vf_bufs_vaddr[0], cam->vf_bufs[0]); + cam->vf_bufs_vaddr[0] = NULL; + cam->vf_bufs[0] = 0; + } + if (cam->vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->vf_bufs_size[1], + cam->vf_bufs_vaddr[1], cam->vf_bufs[1]); + cam->vf_bufs_vaddr[1] = NULL; + cam->vf_bufs[1] = 0; + } + if (cam->rot_vf_bufs_vaddr[0]) { + dma_free_coherent(0, cam->rot_vf_buf_size[0], + cam->rot_vf_bufs_vaddr[0], + cam->rot_vf_bufs[0]); + cam->rot_vf_bufs_vaddr[0] = NULL; + cam->rot_vf_bufs[0] = 0; + } + if (cam->rot_vf_bufs_vaddr[1]) { + dma_free_coherent(0, cam->rot_vf_buf_size[1], + cam->rot_vf_bufs_vaddr[1], + cam->rot_vf_bufs[1]); + cam->rot_vf_bufs_vaddr[1] = NULL; + cam->rot_vf_bufs[1] = 0; + } + + buffer_num = 0; + buffer_ready = 0; + cam->overlay_active = false; + return 0; +} + +/*! + * Enable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_vf_enable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_enable_csi(cam->csi); +} + +/*! + * Disable csi + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_vf_disable_csi(void *private) +{ + cam_data *cam = (cam_data *) private; + + return ipu_disable_csi(cam->csi); +} + +/*! + * function to select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_sdc_select_bg(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->vf_start_sdc = prpvf_start; + cam->vf_stop_sdc = prpvf_stop; + cam->vf_enable_csi = prp_vf_enable_csi; + cam->vf_disable_csi = prp_vf_disable_csi; + cam->overlay_active = false; + } + + return 0; +} + +/*! + * function to de-select PRP-VF as the working path + * + * @param private cam_data * mxc v4l2 main structure + * + * @return status + */ +int prp_vf_sdc_deselect_bg(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + err = prpvf_stop(private); + + if (cam) { + cam->vf_start_sdc = NULL; + cam->vf_stop_sdc = NULL; + cam->vf_enable_csi = NULL; + cam->vf_disable_csi = NULL; + } + return err; +} + +/*! + * Init viewfinder task. + * + * @return Error code indicating success or failure + */ +__init int prp_vf_sdc_init_bg(void) +{ + return 0; +} + +/*! + * Deinit viewfinder task. + * + * @return Error code indicating success or failure + */ +void __exit prp_vf_sdc_exit_bg(void) +{ +} + +module_init(prp_vf_sdc_init_bg); +module_exit(prp_vf_sdc_exit_bg); + +EXPORT_SYMBOL(prp_vf_sdc_select_bg); +EXPORT_SYMBOL(prp_vf_sdc_deselect_bg); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP VF SDC Backgroud Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ipu_still.c b/drivers/media/video/mxc/capture/ipu_still.c new file mode 100644 index 000000000000..ded24623ce2a --- /dev/null +++ b/drivers/media/video/mxc/capture/ipu_still.c @@ -0,0 +1,268 @@ +/* + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ipu_still.c + * + * @brief IPU Use case for still image capture + * + * @ingroup IPU + */ + +#include <linux/ipu.h> +#include <linux/semaphore.h> +#include <linux/ipu.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +static int callback_eof_flag; +#ifndef CONFIG_MXC_IPU_V1 +static int buffer_num; +#endif + +#ifdef CONFIG_MXC_IPU_V1 +static int callback_flag; +/* + * Function definitions + */ +/*! + * CSI EOF callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prp_csi_eof_callback(int irq, void *dev_id) +{ + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + callback_flag%2 ? 1 : 0); + if (callback_flag == 0) + ipu_enable_channel(CSI_MEM); + + callback_flag++; + return IRQ_HANDLED; +} +#endif + +/*! + * CSI callback function. + * + * @param irq int irq line + * @param dev_id void * device id + * + * @return status IRQ_HANDLED for handled + */ +static irqreturn_t prp_still_callback(int irq, void *dev_id) +{ + cam_data *cam = (cam_data *) dev_id; + + callback_eof_flag++; + if (callback_eof_flag < 5) { +#ifndef CONFIG_MXC_IPU_V1 + buffer_num = (buffer_num == 0) ? 1 : 0; + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, buffer_num); +#endif + } else { + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + } + + return IRQ_HANDLED; +} + +/*! + * start csi->mem task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_still_start(void *private) +{ + cam_data *cam = (cam_data *) private; + u32 pixel_fmt; + int err; + ipu_channel_params_t params; + + if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + pixel_fmt = IPU_PIX_FMT_YUV420P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) + pixel_fmt = IPU_PIX_FMT_NV12; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) + pixel_fmt = IPU_PIX_FMT_YUV422P; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) + pixel_fmt = IPU_PIX_FMT_UYVY; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) + pixel_fmt = IPU_PIX_FMT_YUYV; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24) + pixel_fmt = IPU_PIX_FMT_BGR24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB24) + pixel_fmt = IPU_PIX_FMT_RGB24; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB565) + pixel_fmt = IPU_PIX_FMT_RGB565; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR32) + pixel_fmt = IPU_PIX_FMT_BGR32; + else if (cam->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_RGB32) + pixel_fmt = IPU_PIX_FMT_RGB32; + else { + printk(KERN_ERR "format not supported\n"); + return -EINVAL; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_RAW, cam->csi, true, true); + + memset(¶ms, 0, sizeof(params)); + err = ipu_init_channel(CSI_MEM, ¶ms); + if (err != 0) + return err; + + err = ipu_init_channel_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, + pixel_fmt, cam->v2f.fmt.pix.width, + cam->v2f.fmt.pix.height, + cam->v2f.fmt.pix.width, IPU_ROTATE_NONE, + cam->still_buf[0], cam->still_buf[1], + 0, 0); + if (err != 0) + return err; + +#ifdef CONFIG_MXC_IPU_V1 + ipu_clear_irq(IPU_IRQ_SENSOR_OUT_EOF); + err = ipu_request_irq(IPU_IRQ_SENSOR_OUT_EOF, prp_still_callback, + 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR "Error registering irq.\n"); + return err; + } + callback_flag = 0; + callback_eof_flag = 0; + ipu_clear_irq(IPU_IRQ_SENSOR_EOF); + err = ipu_request_irq(IPU_IRQ_SENSOR_EOF, prp_csi_eof_callback, + 0, "Mxc Camera", NULL); + if (err != 0) { + printk(KERN_ERR "Error IPU_IRQ_SENSOR_EOF \n"); + return err; + } +#else + callback_eof_flag = 0; + buffer_num = 0; + + ipu_clear_irq(IPU_IRQ_CSI0_OUT_EOF); + err = ipu_request_irq(IPU_IRQ_CSI0_OUT_EOF, prp_still_callback, + 0, "Mxc Camera", cam); + if (err != 0) { + printk(KERN_ERR "Error registering irq.\n"); + return err; + } + + ipu_select_buffer(CSI_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_enable_channel(CSI_MEM); + ipu_enable_csi(cam->csi); +#endif + + return err; +} + +/*! + * stop csi->mem encoder task + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +static int prp_still_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + +#ifdef CONFIG_MXC_IPU_V1 + ipu_free_irq(IPU_IRQ_SENSOR_EOF, NULL); + ipu_free_irq(IPU_IRQ_SENSOR_OUT_EOF, cam); +#else + ipu_free_irq(IPU_IRQ_CSI0_OUT_EOF, cam); +#endif + + ipu_disable_csi(cam->csi); + ipu_disable_channel(CSI_MEM, true); + ipu_uninit_channel(CSI_MEM); + ipu_csi_enable_mclk_if(CSI_MCLK_RAW, cam->csi, false, false); + + return err; +} + +/*! + * function to select CSI_MEM as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +int prp_still_select(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->csi_start = prp_still_start; + cam->csi_stop = prp_still_stop; + } + + return 0; +} + +/*! + * function to de-select CSI_MEM as the working path + * + * @param private struct cam_data * mxc capture instance + * + * @return status + */ +int prp_still_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + err = prp_still_stop(cam); + + if (cam) { + cam->csi_start = NULL; + cam->csi_stop = NULL; + } + + return err; +} + +/*! + * Init the Encorder channels + * + * @return Error code indicating success or failure + */ +__init int prp_still_init(void) +{ + return 0; +} + +/*! + * Deinit the Encorder channels + * + */ +void __exit prp_still_exit(void) +{ +} + +module_init(prp_still_init); +module_exit(prp_still_exit); + +EXPORT_SYMBOL(prp_still_select); +EXPORT_SYMBOL(prp_still_deselect); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IPU PRP STILL IMAGE Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mc521da.c b/drivers/media/video/mxc/capture/mc521da.c new file mode 100644 index 000000000000..a8ff84fb4c84 --- /dev/null +++ b/drivers/media/video/mxc/capture/mc521da.c @@ -0,0 +1,648 @@ +/* + * Copyright 2006-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc521da.c + * + * @brief MC521DA camera driver functions + * + * @ingroup Camera + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include "mxc_v4l2_capture.h" + +#define MC521DA_I2C_ADDRESS 0x22 +#define MC521DA_TERM 0xFF + +typedef struct { + u16 width; + u16 height; +} mc521da_image_format; + +struct mc521da_reg { + u8 reg; + u8 val; +}; + +static sensor_interface *interface_param = NULL; + +static mc521da_image_format format[2] = { + { + .width = 1600, + .height = 1200, + }, + { + .width = 640, + .height = 480, + }, +}; + +const static struct mc521da_reg mc521da_initial[] = { + /*---------------------------------------------------------- + * Sensor Setting Start + *---------------------------------------------------------- + */ + {0xff, 0x01}, /* Sensor setting start */ + {0x01, 0x10}, /* Wavetable script, generated by waveman */ + {0x10, 0x64}, + {0x03, 0x00}, {0x04, 0x06}, {0x05, 0x30}, {0x06, 0x02}, {0x08, 0x00}, + {0x03, 0x01}, {0x04, 0x41}, {0x05, 0x70}, {0x06, 0x03}, {0x08, 0x00}, + {0x03, 0x02}, {0x04, 0x55}, {0x05, 0x30}, {0x06, 0x03}, {0x08, 0x00}, + {0x03, 0x03}, {0x04, 0x5A}, {0x05, 0x30}, {0x06, 0x02}, {0x08, 0x00}, + {0x03, 0x04}, {0x04, 0x7A}, {0x05, 0x30}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x05}, {0x04, 0x9C}, {0x05, 0x30}, {0x06, 0x0F}, {0x08, 0x00}, + {0x03, 0x06}, {0x04, 0x73}, {0x05, 0x31}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x07}, {0x04, 0x2D}, {0x05, 0x3B}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x08}, {0x04, 0x32}, {0x05, 0x33}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x09}, {0x04, 0x67}, {0x05, 0x63}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x0a}, {0x04, 0x6C}, {0x05, 0x23}, {0x06, 0x0E}, {0x08, 0x00}, + {0x03, 0x0b}, {0x04, 0x71}, {0x05, 0x23}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x0c}, {0x04, 0x30}, {0x05, 0x2F}, {0x06, 0x06}, {0x08, 0x00}, + {0x03, 0x0d}, {0x04, 0x00}, {0x05, 0x00}, {0x06, 0x06}, {0x08, 0x00}, + {0x07, 0x0e}, + + /* Start Address */ + {0x10, 0x64}, {0x14, 0x10}, {0x15, 0x00}, + + /* SYNC */ + {0x18, 0x40}, {0x19, 0x00}, {0x1A, 0x03}, {0x1B, 0x00}, + + /* X-Y Mirror */ + {0x11, 0x00}, {0xda, 0x00}, /* X mirror OFF, Y Mirror OFF */ + + /* Frame height */ + {0x1c, 0x13}, {0x1d, 0x04}, {0x0e, 0x4b}, {0x0f, 0x05}, + {0x9e, 0x04}, {0x9d, 0xc6}, {0xcc, 0x14}, {0xcd, 0x05}, + + /* Frame width */ + {0x0c, 0x35}, {0x0d, 0x07}, {0x9b, 0x10}, {0x9c, 0x07}, + {0x93, 0x21}, + + {0x01, 0x01}, {0x40, 0x00}, {0x41, 0x00}, {0x42, 0xf0}, + {0x43, 0x03}, {0x44, 0x0a}, {0x45, 0x00}, {0x3b, 0x40}, + {0x38, 0x18}, {0x3c, 0x00}, {0x20, 0x00}, {0x21, 0x01}, + {0x22, 0x00}, {0x23, 0x01}, {0x24, 0x00}, {0x25, 0x01}, + {0x26, 0x00}, {0x27, 0x01}, {0xb9, 0x04}, {0xb8, 0xc3}, + {0xbb, 0x04}, {0xba, 0xc3}, {0xbf, 0x04}, {0xbe, 0xc3}, + + /* Ramp */ + {0x57, 0x07}, {0x56, 0xd6}, {0x55, 0x03}, {0x54, 0x74}, + {0x9f, 0x99}, {0x94, 0x80}, {0x91, 0x78}, {0x92, 0x8b}, + + /* Output Mode */ + {0x52, 0x10}, {0x51, 0x00}, + + /* Analog Gain and Output driver */ + {0x28, 0x00}, {0xdd, 0x82}, {0xdb, 0x00}, {0xdc, 0x00}, + + /* Update */ + {0x00, 0x84}, + + /* PLL ADC clock = 75 MHz */ + {0xb5, 0x60}, {0xb4, 0x02}, {0xb5, 0x20}, + + /*----------------------------------------------*/ + /* ISP Setting Start */ + /*----------------------------------------------*/ + {0xff, 0x02}, + {0x01, 0xbd}, {0x02, 0xf8}, {0x03, 0x3a}, {0x04, 0x00}, {0x0e, 0x00}, + + /* Output mode */ + {0x88, 0x00}, {0x87, 0x11}, + + /* Threshold */ + {0xb6, 0x1b}, {0x0d, 0xc0}, {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00}, + + /* Image Effect */ + {0x3f, 0x80}, {0x40, 0x00}, {0x41, 0x00}, {0x42, 0x80}, {0x43, 0x00}, + {0x44, 0x00}, {0x45, 0x00}, {0x46, 0x00}, {0x56, 0x80}, {0x57, 0x20}, + {0x58, 0x20}, {0x59, 0x02}, {0x5a, 0x00}, {0x5b, 0x78}, {0x5c, 0x7c}, + {0x5d, 0x84}, {0x5e, 0x85}, {0x5f, 0x78}, {0x60, 0x7e}, {0x61, 0x82}, + {0x62, 0x85}, {0x63, 0x00}, {0x64, 0x80}, {0x65, 0x00}, {0x66, 0x80}, + {0x67, 0x80}, {0x68, 0x80}, + + /* Auto Focus */ + {0x6e, 0x02}, {0x6f, 0xe5}, {0x70, 0x08}, {0x71, 0x01}, {0x72, 0x00}, + + /* Decimator */ + {0x78, 0xff}, {0x79, 0xff}, {0x7a, 0x70}, {0x7b, 0x00}, {0x7c, 0x00}, + {0x7d, 0x00}, {0x7e, 0xc8}, {0x7f, 0xc8}, {0x80, 0x96}, {0x81, 0x96}, + {0x82, 0x00}, {0x83, 0x00}, {0x84, 0x00}, {0x85, 0x00}, {0x86, 0x00}, + + /* Luminance Info */ + {0xf9, 0x20}, {0xb7, 0x7f}, {0xb8, 0x28}, {0xb9, 0x08}, + {0xf9, 0xa0}, {0xb7, 0x10}, {0xb9, 0x00}, + {0xf9, 0x40}, {0xb7, 0x7f}, {0xb8, 0x28}, {0xb9, 0x08}, + {0xf9, 0xc0}, {0xb7, 0x08}, {0xb9, 0x00}, + {0xf9, 0x60}, {0xb7, 0x7f}, {0xb8, 0x28}, {0xb9, 0x08}, + {0xf9, 0xe0}, {0xb7, 0x05}, {0xb9, 0x00}, + {0xf9, 0x00}, {0xb7, 0x03}, {0xb8, 0x2d}, {0xb9, 0xcd}, + {0xf9, 0x80}, {0xb7, 0x02}, {0xb9, 0x00}, + + /* AE */ + {0x8a, 0x00}, {0x89, 0xc0}, {0x8c, 0x32}, {0x8d, 0x96}, {0x8e, 0x25}, + {0x8f, 0x70}, {0x90, 0x12}, {0x91, 0x41}, {0x9e, 0x2e}, {0x9f, 0x2e}, + {0xa0, 0x0b}, {0xa1, 0x71}, {0xa2, 0xb0}, {0xa3, 0x09}, {0xa4, 0x89}, + {0xa5, 0x68}, {0xa6, 0x1a}, {0xa7, 0xb3}, {0xa8, 0xf0}, {0xa9, 0x19}, + {0xaa, 0x6a}, {0xab, 0x6b}, {0xac, 0x01}, {0xad, 0xe8}, {0xae, 0x48}, + {0xaf, 0x01}, {0xb0, 0x96}, {0xb1, 0xe6}, {0xb2, 0x03}, {0xb3, 0x00}, + {0xb4, 0x10}, {0xb5, 0x00}, {0xb6, 0x04}, {0xba, 0x44}, {0xbb, 0x3a}, + {0xbc, 0x01}, {0xbd, 0x08}, {0xbe, 0xa0}, {0xbf, 0x01}, {0xc0, 0x82}, + {0x8a, 0xe1}, {0x8b, 0x8c}, + + /* AWB */ + {0xc8, 0x00}, {0xc9, 0x00}, {0xca, 0x40}, {0xcb, 0xB0}, {0xcc, 0x40}, + {0xcd, 0xff}, {0xce, 0x19}, {0xcf, 0x40}, {0xd0, 0x01}, {0xd1, 0x43}, + {0xd2, 0x80}, {0xd3, 0x80}, {0xd4, 0xf1}, {0xdf, 0x00}, {0xe0, 0x8f}, + {0xe1, 0x8f}, {0xe2, 0x53}, {0xe3, 0x97}, {0xe4, 0x1f}, {0xe5, 0x3b}, + {0xe6, 0x9c}, {0xe7, 0x2e}, {0xe8, 0x03}, {0xe9, 0x02}, + + /* Neutral CCM */ + {0xfa, 0x00}, {0xd5, 0x3f}, {0xd6, 0x8c}, {0xd7, 0x43}, {0xd8, 0x08}, + {0xd9, 0x27}, {0xda, 0x7e}, {0xdb, 0x17}, {0xdc, 0x1a}, {0xdd, 0x47}, + {0xde, 0xa1}, + + /* Blue CCM */ + {0xfa, 0x01}, {0xd5, 0x3f}, {0xd6, 0x77}, {0xd7, 0x34}, {0xd8, 0x03}, + {0xd9, 0x18}, {0xda, 0x6e}, {0xdb, 0x16}, {0xdc, 0x0f}, {0xdd, 0x29}, + {0xde, 0x77}, + + /* Red CCM */ + {0xfa, 0x02}, {0xd5, 0x3f}, {0xd6, 0x7d}, {0xd7, 0x2f}, {0xd8, 0x0e}, + {0xd9, 0x1e}, {0xda, 0x76}, {0xdb, 0x18}, {0xdc, 0x29}, {0xdd, 0x51}, + {0xde, 0xba}, + + /* AWB */ + {0xea, 0x00}, {0xeb, 0x1a}, {0xc8, 0x33}, {0xc9, 0xc2}, + + {0xed, 0x02}, {0xee, 0x02}, + + /* AFD */ + {0xf0, 0x11}, {0xf1, 0x03}, {0xf2, 0x05}, {0xf5, 0x05}, {0xf6, 0x32}, + {0xf7, 0x32}, + + /* Lens Shading */ + {0xf9, 0x00}, {0x05, 0x04}, {0x06, 0xff}, {0x07, 0xf2}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xf2}, {0x0b, 0xff}, {0x0c, 0xff}, + {0xf9, 0x01}, {0x05, 0x04}, {0x06, 0xff}, {0x07, 0x8b}, {0x08, 0x16}, + {0x09, 0x16}, {0x0a, 0x8b}, {0x0b, 0xff}, {0x0c, 0xe0}, + {0xf9, 0x02}, {0x05, 0x04}, {0x06, 0xff}, {0x07, 0x8b}, {0x08, 0x16}, + {0x09, 0x16}, {0x0a, 0x8b}, {0x0b, 0xff}, {0x0c, 0xe0}, + {0xf9, 0x03}, {0x05, 0x04}, {0x06, 0xff}, {0x07, 0x7c}, {0x08, 0x26}, + {0x09, 0x26}, {0x0a, 0x7c}, {0x0b, 0xd0}, {0x0c, 0xe0}, + {0xf9, 0x04}, {0x05, 0x0d}, {0x06, 0x40}, {0x07, 0xa0}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xa0}, {0x0b, 0x40}, {0x0c, 0xe0}, + {0xf9, 0x05}, {0x05, 0x0d}, {0x06, 0x40}, {0x07, 0xa0}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xa0}, {0x0b, 0x40}, {0x0c, 0xa0}, + {0xf9, 0x06}, {0x05, 0x0d}, {0x06, 0x40}, {0x07, 0xa0}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xa0}, {0x0b, 0x40}, {0x0c, 0xa0}, + {0xf9, 0x07}, {0x05, 0x0d}, {0x06, 0x40}, {0x07, 0xa0}, {0x08, 0x00}, + {0x09, 0x00}, {0x0a, 0xa0}, {0x0b, 0x40}, {0x0c, 0xa0}, + + /* Edge setting */ + {0x73, 0x68}, {0x74, 0x40}, {0x75, 0x00}, {0x76, 0xff}, {0x77, 0x80}, + {0x4f, 0x80}, {0x50, 0x82}, {0x51, 0x82}, {0x52, 0x08}, + + /* Interpolation Setting */ + {0x23, 0x7f}, {0x22, 0x08}, {0x18, 0xff}, {0x19, 0x00}, + {0x40, 0x00}, {0x53, 0xff}, {0x54, 0x0a}, {0x55, 0xc2}, + {0x1b, 0x18}, + + {0xfa, 0x00}, {0x15, 0x0c}, {0x22, 0x00}, {0x0e, 0xef}, {0x1f, 0x1d}, + {0x20, 0x2d}, {0x1c, 0x01}, {0x1d, 0x02}, {0x1e, 0x03}, {0x0e, 0xee}, + {0x12, 0x10}, {0x16, 0x10}, {0x17, 0x02}, {0x1a, 0x01}, + {0xfa, 0x04}, {0x0e, 0xef}, {0x1c, 0x01}, {0x1d, 0x02}, {0x1e, 0x03}, + {0x1f, 0x11}, {0x20, 0x11}, {0x0e, 0xee}, {0x12, 0x03}, {0x16, 0x10}, + {0x17, 0x02}, {0x1a, 0xee}, + {0xfa, 0x08}, {0x0e, 0xef}, {0x1c, 0x01}, {0x1d, 0x02}, {0x1e, 0x03}, + {0x1f, 0x00}, {0x20, 0x00}, {0x0e, 0xee}, {0x12, 0x03}, {0x16, 0x10}, + {0x17, 0x02}, {0x1a, 0x22}, + + /* Gamma Correction */ + {0x27, 0x62}, {0x28, 0x00}, {0x27, 0x62}, {0x28, 0x00}, {0x29, 0x00}, + {0x2a, 0x00}, {0x2f, 0x03}, {0x30, 0x10}, {0x31, 0x2b}, {0x32, 0x50}, + {0x33, 0x70}, {0x34, 0x90}, {0x35, 0xB0}, {0x36, 0xD0}, {0x37, 0x00}, + {0x38, 0x18}, {0x39, 0x57}, {0x3a, 0x89}, {0x3b, 0xac}, {0x3c, 0xc9}, + {0x3d, 0xde}, {0x3e, 0xef}, {0x2b, 0x00}, {0x2c, 0x00}, {0x2d, 0x40}, + {0x2e, 0xab}, + + /* Contrast */ + {0x47, 0x10}, {0x48, 0x1f}, {0x49, 0xe3}, {0x4a, 0xf0}, {0x4b, 0x08}, + {0x4c, 0x14}, {0x4d, 0xe9}, {0x4e, 0xf5}, {0x98, 0x8a}, + + {0xfa, 0x00}, + {MC521DA_TERM, MC521DA_TERM} +}; + +static int mc521da_attach(struct i2c_adapter *adapter); +static int mc521da_detach(struct i2c_client *client); + +static struct i2c_driver mc521da_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "MC521DA Client", + }, + .attach_adapter = mc521da_attach, + .detach_client = mc521da_detach, +}; + +static struct i2c_client mc521da_i2c_client = { + .name = "MC521DA I2C dev", + .addr = MC521DA_I2C_ADDRESS, + .driver = &mc521da_i2c_driver, +}; + +static inline int mc521da_read_reg(u8 reg) +{ + return i2c_smbus_read_byte_data(&mc521da_i2c_client, reg); +} + +static inline int mc521da_write_reg(u8 reg, u8 val) +{ + return i2c_smbus_write_byte_data(&mc521da_i2c_client, reg, val); +} + +static int mc521da_write_regs(const struct mc521da_reg reglist[]) +{ + int err; + const struct mc521da_reg *next = reglist; + + while (!((next->reg == MC521DA_TERM) && (next->val == MC521DA_TERM))) { + err = mc521da_write_reg(next->reg, next->val); + if (err) { + return err; + } + next++; + } + return 0; +} + +/*! + * mc521da sensor downscale function + * @param downscale bool + * @return Error code indicating success or failure + */ +static u8 mc521da_sensor_downscale(bool downscale) +{ + u8 data; + u32 i = 0; + + if (downscale == true) { + // VGA + mc521da_write_reg(0xff, 0x01); + + mc521da_write_reg(0x52, 0x30); + mc521da_write_reg(0x51, 0x00); + + mc521da_write_reg(0xda, 0x01); + mc521da_write_reg(0x00, 0x8C); + + /* Wait for changes to take effect */ + while (i < 256) { + i++; + data = mc521da_read_reg(0x00); + if ((data & 0x80) == 0) + break; + msleep(5); + } + + /* ISP */ + mc521da_write_reg(0xff, 0x02); + + mc521da_write_reg(0x03, 0x3b); /* Enable Decimator */ + + mc521da_write_reg(0x7a, 0x74); + mc521da_write_reg(0x7b, 0x01); + mc521da_write_reg(0x7e, 0x50); + mc521da_write_reg(0x7f, 0x50); + mc521da_write_reg(0x80, 0x3c); + mc521da_write_reg(0x81, 0x3c); + } else { + //UXGA + mc521da_write_reg(0xff, 0x01); + mc521da_write_reg(0x52, 0x10); + mc521da_write_reg(0x51, 0x00); + mc521da_write_reg(0xda, 0x00); + + /* update */ + mc521da_write_reg(0x00, 0x84); + + /* Wait for changes to take effect */ + while (i < 256) { + i++; + data = mc521da_read_reg(0x00); + if ((data & 0x80) == 0) + break; + msleep(5); + } + + /* ISP */ + mc521da_write_reg(0xff, 0x02); + + mc521da_write_reg(0x03, 0x3a); + + mc521da_write_reg(0x7a, 0x70); + mc521da_write_reg(0x7b, 0x00); + mc521da_write_reg(0x7e, 0xc8); + mc521da_write_reg(0x7f, 0xc8); + mc521da_write_reg(0x80, 0x96); + mc521da_write_reg(0x81, 0x96); + } + + return 0; +} + +/*! + * mc521da sensor interface Initialization + * @param param sensor_interface * + * @param width u32 + * @param height u32 + * @return None + */ +static void mc521da_interface(sensor_interface * param, u32 width, u32 height) +{ + param->clk_mode = 0x0; //gated + param->pixclk_pol = 0x0; + param->data_width = 0x1; + param->data_pol = 0x0; + param->ext_vsync = 0x0; + param->Vsync_pol = 0x0; + param->Hsync_pol = 0x0; + param->width = width - 1; + param->height = height - 1; + param->active_width = width; + param->active_height = height; + param->pixel_fmt = IPU_PIX_FMT_UYVY; +} + +extern void gpio_sensor_reset(bool flag); + +/*! + * mc521da Reset function + * + * @return None + */ +static sensor_interface *mc521da_reset(void) +{ + if (interface_param == NULL) + return NULL; + + mc521da_interface(interface_param, format[1].width, format[1].height); + set_mclk_rate(&interface_param->mclk); + + gpio_sensor_reset(true); + msleep(10); + gpio_sensor_reset(false); + msleep(50); + + return interface_param; +} + +/*! + * mc521da sensor configuration + * + * @param frame_rate int * + * @param high_quality int + * @return sensor_interface * + */ +static sensor_interface *mc521da_config(int *frame_rate, int high_quality) +{ + int num_clock_per_row, err; + int max_rate = 0; + int index = 1; + u16 frame_height; + + if (high_quality == 1) + index = 0; + + err = mc521da_write_regs(mc521da_initial); + if (err) { + /* Reduce the MCLK */ + interface_param->mclk = 20000000; + mc521da_reset(); + + printk(KERN_INFO "mc521da: mclk reduced\n"); + mc521da_write_regs(mc521da_initial); + } + + mc521da_interface(interface_param, format[index].width, + format[index].height); + + if (index == 0) { + mc521da_sensor_downscale(false); + } else { + mc521da_sensor_downscale(true); + } + + num_clock_per_row = 1845; + max_rate = interface_param->mclk * 3 * (index + 1) + / (2 * num_clock_per_row * 1300); + + if ((*frame_rate > max_rate) || (*frame_rate == 0)) { + *frame_rate = max_rate; + } + + frame_height = 1300 * max_rate / (*frame_rate); + + *frame_rate = interface_param->mclk * 3 * (index + 1) + / (2 * num_clock_per_row * frame_height); + + mc521da_write_reg(0xff, 0x01); + mc521da_write_reg(0xE, frame_height & 0xFF); + mc521da_write_reg(0xF, (frame_height & 0xFF00) >> 8); + mc521da_write_reg(0xCC, frame_height & 0xFF); + mc521da_write_reg(0xCD, (frame_height & 0xFF00) >> 8); + + return interface_param; +} + +/*! + * mc521da sensor set color configuration + * + * @param bright int + * @param saturation int + * @param red int + * @param green int + * @param blue int + * @return None + */ +static void +mc521da_set_color(int bright, int saturation, int red, int green, int blue) +{ + /* Select ISP */ + mc521da_write_reg(0xff, 0x02); + + mc521da_write_reg(0x41, bright); + mc521da_write_reg(0xca, red); + mc521da_write_reg(0xcb, green); + mc521da_write_reg(0xcc, blue); +} + +/*! + * mc521da sensor get color configuration + * + * @param bright int * + * @param saturation int * + * @param red int * + * @param green int * + * @param blue int * + * @return None + */ +static void +mc521da_get_color(int *bright, int *saturation, int *red, int *green, int *blue) +{ + *saturation = 0; + + /* Select ISP */ + mc521da_write_reg(0xff, 0x02); + + *bright = mc521da_read_reg(0x41); + *red = mc521da_read_reg(0xCA); + *green = mc521da_read_reg(0xCB); + *blue = mc521da_read_reg(0xCC); +} + +struct camera_sensor camera_sensor_if = { + set_color:mc521da_set_color, + get_color:mc521da_get_color, + config:mc521da_config, + reset:mc521da_reset, +}; + +/*! + * mc521da I2C detect_client function + * + * @param adapter struct i2c_adapter * + * @param address int + * @param kind int + * + * @return Error code indicating success or failure + */ +static int mc521da_detect_client(struct i2c_adapter *adapter, int address, + int kind) +{ + mc521da_i2c_client.adapter = adapter; + if (i2c_attach_client(&mc521da_i2c_client)) { + mc521da_i2c_client.adapter = NULL; + printk(KERN_ERR "mc521da_attach: i2c_attach_client failed\n"); + return -1; + } + + interface_param = (sensor_interface *) + kmalloc(sizeof(sensor_interface), GFP_KERNEL); + if (!interface_param) { + printk(KERN_ERR "mc521da_attach: kmalloc failed \n"); + return -1; + } + + interface_param->mclk = 25000000; + + printk(KERN_INFO "mc521da Detected\n"); + + return 0; +} + +static unsigned short normal_i2c[] = { MC521DA_I2C_ADDRESS, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static int mc521da_attach(struct i2c_adapter *adap) +{ + uint32_t mclk = 25000000; + struct clk *clk; + int err; + + clk = clk_get(NULL, "csi_clk"); + clk_enable(clk); + set_mclk_rate(&mclk); + + gpio_sensor_reset(true); + msleep(10); + gpio_sensor_reset(false); + msleep(100); + + err = i2c_probe(adap, &addr_data, &mc521da_detect_client); + + clk_disable(clk); + clk_put(clk); + + return err; +} + +/*! + * mc521da I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int mc521da_detach(struct i2c_client *client) +{ + int err; + + if (!mc521da_i2c_client.adapter) + return -1; + + err = i2c_detach_client(&mc521da_i2c_client); + mc521da_i2c_client.adapter = NULL; + + if (interface_param) + kfree(interface_param); + interface_param = NULL; + + return err; +} + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +/*! + * mc521da init function + * + * @return Error code indicating success or failure + */ +static __init int mc521da_init(void) +{ + gpio_sensor_active(); + return i2c_add_driver(&mc521da_i2c_driver); +} + +/*! + * mc521da cleanup function + * + * @return Error code indicating success or failure + */ +static void __exit mc521da_clean(void) +{ + i2c_del_driver(&mc521da_i2c_driver); + gpio_sensor_inactive(); +} + +module_init(mc521da_init); +module_exit(mc521da_clean); + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(camera_sensor_if); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MC521DA Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mt9v111.c b/drivers/media/video/mxc/capture/mt9v111.c new file mode 100644 index 000000000000..c95a20683924 --- /dev/null +++ b/drivers/media/video/mxc/capture/mt9v111.c @@ -0,0 +1,1076 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mt9v111.c + * + * @brief mt9v111 camera driver functions + * + * @ingroup Camera + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <media/v4l2-int-device.h> +#include "mxc_v4l2_capture.h" +#include "mt9v111.h" + +#ifdef MT9V111_DEBUG +static u16 testpattern = 0; +#endif + +static mt9v111_conf mt9v111_device; + +/*! + * Holds the current frame rate. + */ +static int reset_frame_rate = MT9V111_FRAME_RATE; + +struct sensor { + const struct mt9v111_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + +} mt9v111_data; + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +static int mt9v111_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int mt9v111_remove(struct i2c_client *client); + +static const struct i2c_device_id mt9v111_id[] = { + {"mt9v111", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mt9v111_id); + +static struct i2c_driver mt9v111_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "mt9v111", + }, + .probe = mt9v111_probe, + .remove = mt9v111_remove, + .id_table = mt9v111_id, +/* To add power management add .suspend and .resume functions */ +}; + +/* + * Function definitions + */ + +#ifdef MT9V111_DEBUG +static inline int mt9v111_read_reg(u8 reg) +{ + int val = i2c_smbus_read_word_data(mt9v111_data.i2c_client, reg); + if (val != -1) + val = cpu_to_be16(val); + return val; +} +#endif + +/*! + * Writes to the register via I2C. + */ +static inline int mt9v111_write_reg(u8 reg, u16 val) +{ + pr_debug("In mt9v111_write_reg (0x%x, 0x%x)\n", reg, val); + pr_debug(" write reg %x val %x.\n", reg, val); + + return i2c_smbus_write_word_data(mt9v111_data.i2c_client, + reg, cpu_to_be16(val)); +} + +/*! + * Initialize mt9v111_sensor_lib + * Libarary for Sensor configuration through I2C + * + * @param coreReg Core Registers + * @param ifpReg IFP Register + * + * @return status + */ +static u8 mt9v111_sensor_lib(mt9v111_coreReg * coreReg, mt9v111_IFPReg * ifpReg) +{ + u8 reg; + u16 data; + u8 error = 0; + + pr_debug("In mt9v111_sensor_lib\n"); + + /* + * setup to IFP registers + */ + reg = MT9V111I_ADDR_SPACE_SEL; + data = ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + /* Operation Mode Control */ + reg = MT9V111I_MODE_CONTROL; + data = ifpReg->modeControl; + mt9v111_write_reg(reg, data); + + /* Output format */ + reg = MT9V111I_FORMAT_CONTROL; + data = ifpReg->formatControl; /* Set bit 12 */ + mt9v111_write_reg(reg, data); + + /* AE limit 4 */ + reg = MT9V111I_SHUTTER_WIDTH_LIMIT_AE; + data = ifpReg->gainLimitAE; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_OUTPUT_FORMAT_CTRL2; + data = ifpReg->outputFormatCtrl2; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_AE_SPEED; + data = ifpReg->AESpeed; + mt9v111_write_reg(reg, data); + + /* output image size */ + reg = MT9V111i_H_PAN; + data = 0x8000 | ifpReg->HPan; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_ZOOM; + data = 0x8000 | ifpReg->HZoom; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_SIZE; + data = 0x8000 | ifpReg->HSize; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_PAN; + data = 0x8000 | ifpReg->VPan; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_ZOOM; + data = 0x8000 | ifpReg->VZoom; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_V_SIZE; + data = 0x8000 | ifpReg->VSize; + mt9v111_write_reg(reg, data); + + reg = MT9V111i_H_PAN; + data = ~0x8000 & ifpReg->HPan; + mt9v111_write_reg(reg, data); +#if 0 + reg = MT9V111I_UPPER_SHUTTER_DELAY_LIM; + data = ifpReg->upperShutterDelayLi; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_SHUTTER_60; + data = ifpReg->shutter_width_60; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_SEARCH_FLICK_60; + data = ifpReg->search_flicker_60; + mt9v111_write_reg(reg, data); +#endif + + /* + * setup to sensor core registers + */ + reg = MT9V111I_ADDR_SPACE_SEL; + data = coreReg->addressSelect; + mt9v111_write_reg(reg, data); + + /* enable changes and put the Sync bit on */ + reg = MT9V111S_OUTPUT_CTRL; + data = MT9V111S_OUTCTRL_SYNC | MT9V111S_OUTCTRL_CHIP_ENABLE | 0x3000; + mt9v111_write_reg(reg, data); + + /* min PIXCLK - Default */ + reg = MT9V111S_PIXEL_CLOCK_SPEED; + data = coreReg->pixelClockSpeed; + mt9v111_write_reg(reg, data); + + /* Setup image flipping / Dark rows / row/column skip */ + reg = MT9V111S_READ_MODE; + data = coreReg->readMode; + mt9v111_write_reg(reg, data); + + /* zoom 0 */ + reg = MT9V111S_DIGITAL_ZOOM; + data = coreReg->digitalZoom; + mt9v111_write_reg(reg, data); + + /* min H-blank */ + reg = MT9V111S_HOR_BLANKING; + data = coreReg->horizontalBlanking; + mt9v111_write_reg(reg, data); + + /* min V-blank */ + reg = MT9V111S_VER_BLANKING; + data = coreReg->verticalBlanking; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_SHUTTER_WIDTH; + data = coreReg->shutterWidth; + mt9v111_write_reg(reg, data); + + reg = MT9V111S_SHUTTER_DELAY; + data = ifpReg->upperShutterDelayLi; + mt9v111_write_reg(reg, data); + + /* changes become effective */ + reg = MT9V111S_OUTPUT_CTRL; + data = MT9V111S_OUTCTRL_CHIP_ENABLE | 0x3000; + mt9v111_write_reg(reg, data); + + return error; +} + +/*! + * MT9V111 frame rate calculate + * + * @param frame_rate int * + * @param mclk int + * @return None + */ +static void mt9v111_rate_cal(int *frame_rate, int mclk) +{ + int num_clock_per_row; + int max_rate = 0; + + pr_debug("In mt9v111_rate_cal\n"); + + num_clock_per_row = (MT9V111_MAX_WIDTH + 114 + MT9V111_HORZBLANK_MIN) + * 2; + max_rate = mclk / (num_clock_per_row * + (MT9V111_MAX_HEIGHT + MT9V111_VERTBLANK_DEFAULT)); + + if ((*frame_rate > max_rate) || (*frame_rate == 0)) { + *frame_rate = max_rate; + } + + mt9v111_device.coreReg->verticalBlanking + = mclk / (*frame_rate * num_clock_per_row) - MT9V111_MAX_HEIGHT; + + reset_frame_rate = *frame_rate; +} + +/*! + * MT9V111 sensor configuration + */ +void mt9v111_config(void) +{ + pr_debug("In mt9v111_config\n"); + + mt9v111_device.coreReg->addressSelect = MT9V111I_SEL_SCA; + mt9v111_device.ifpReg->addrSpaceSel = MT9V111I_SEL_IFP; + + mt9v111_device.coreReg->windowHeight = MT9V111_WINHEIGHT; + mt9v111_device.coreReg->windowWidth = MT9V111_WINWIDTH; + mt9v111_device.coreReg->zoomColStart = 0; + mt9v111_device.coreReg->zomRowStart = 0; + mt9v111_device.coreReg->digitalZoom = 0x0; + + mt9v111_device.coreReg->verticalBlanking = MT9V111_VERTBLANK_DEFAULT; + mt9v111_device.coreReg->horizontalBlanking = MT9V111_HORZBLANK_MIN; + mt9v111_device.coreReg->pixelClockSpeed = 0; + mt9v111_device.coreReg->readMode = 0xd0a1; + + mt9v111_device.ifpReg->outputFormatCtrl2 = 0; + mt9v111_device.ifpReg->gainLimitAE = 0x300; + mt9v111_device.ifpReg->AESpeed = 0x80; + + /* here is the default value */ + mt9v111_device.ifpReg->formatControl = 0xc800; + mt9v111_device.ifpReg->modeControl = 0x708e; + mt9v111_device.ifpReg->awbSpeed = 0x4514; + mt9v111_device.coreReg->shutterWidth = 0xf8; + + /* output size */ + mt9v111_device.ifpReg->HPan = 0; + mt9v111_device.ifpReg->HZoom = MT9V111_MAX_WIDTH; + mt9v111_device.ifpReg->HSize = MT9V111_MAX_WIDTH; + mt9v111_device.ifpReg->VPan = 0; + mt9v111_device.ifpReg->VZoom = MT9V111_MAX_HEIGHT; + mt9v111_device.ifpReg->VSize = MT9V111_MAX_HEIGHT; +} + +/*! + * mt9v111 sensor set saturtionn + * + * @param saturation int + + * @return Error code of 0. + */ +static int mt9v111_set_saturation(int saturation) +{ + u8 reg; + u16 data; + pr_debug("In mt9v111_set_saturation(%d)\n", + saturation); + + switch (saturation) { + case 150: + mt9v111_device.ifpReg->awbSpeed = 0x6D14; + break; + case 100: + mt9v111_device.ifpReg->awbSpeed = 0x4514; + break; + case 75: + mt9v111_device.ifpReg->awbSpeed = 0x4D14; + break; + case 50: + mt9v111_device.ifpReg->awbSpeed = 0x5514; + break; + case 37: + mt9v111_device.ifpReg->awbSpeed = 0x5D14; + break; + case 25: + mt9v111_device.ifpReg->awbSpeed = 0x6514; + break; + default: + mt9v111_device.ifpReg->awbSpeed = 0x4514; + break; + } + + reg = MT9V111I_ADDR_SPACE_SEL; + data = mt9v111_device.ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + /* Operation Mode Control */ + reg = MT9V111I_AWB_SPEED; + data = mt9v111_device.ifpReg->awbSpeed; + mt9v111_write_reg(reg, data); + + return 0; +} + +/*! + * mt9v111 sensor set Auto Exposure measurement window mode configuration + * + * @param ae_mode int + * @return Error code of 0 (no Error) + */ +static int mt9v111_set_ae_mode(int ae_mode) +{ + u8 reg; + u16 data; + + pr_debug("In mt9v111_set_ae_mode(%d)\n", + ae_mode); + + /* Currently this driver only supports auto and manual exposure + * modes. */ + if ((ae_mode > 1) || (ae_mode << 0)) + return -EPERM; + + /* + * The auto exposure is set in bit 14. + * Other values are set for: + * -on the fly defect correction is on (bit 13). + * -aperature correction knee enabled (bit 12). + * -ITU_R BT656 synchronization codes are embedded in the image (bit 7) + * -AE measurement window is weighted sum of large and center windows + * (bits 2-3). + * -auto white balance is on (bit 1). + * -normal color processing (bit 4 = 0). + */ + /* V4L2_EXPOSURE_AUTO = 0; needs register setting of 0x708E */ + /* V4L2_EXPOSURE_MANUAL = 1 needs register setting of 0x308E */ + mt9v111_device.ifpReg->modeControl &= 0x3fff; + mt9v111_device.ifpReg->modeControl |= (ae_mode & 0x03) << 14; + mt9v111_data.ae_mode = ae_mode; + + reg = MT9V111I_ADDR_SPACE_SEL; + data = mt9v111_device.ifpReg->addrSpaceSel; + mt9v111_write_reg(reg, data); + + reg = MT9V111I_MODE_CONTROL; + data = mt9v111_device.ifpReg->modeControl; + mt9v111_write_reg(reg, data); + + return 0; +} + +/*! + * mt9v111 sensor get AE measurement window mode configuration + * + * @param ae_mode int * + * @return None + */ +static void mt9v111_get_ae_mode(int *ae_mode) +{ + pr_debug("In mt9v111_get_ae_mode(%d)\n", *ae_mode); + + if (ae_mode != NULL) { + *ae_mode = (mt9v111_device.ifpReg->modeControl & 0xc) >> 2; + } +} + +#ifdef MT9V111_DEBUG +/*! + * Set sensor to test mode, which will generate test pattern. + * + * @return none + */ +static void mt9v111_test_pattern(bool flag) +{ + u16 data; + + /* switch to sensor registers */ + mt9v111_write_reg(MT9V111I_ADDR_SPACE_SEL, MT9V111I_SEL_SCA); + + if (flag == true) { + testpattern = MT9V111S_OUTCTRL_TEST_MODE; + + data = mt9v111_read_reg(MT9V111S_ROW_NOISE_CTRL) & 0xBF; + mt9v111_write_reg(MT9V111S_ROW_NOISE_CTRL, data); + + mt9v111_write_reg(MT9V111S_TEST_DATA, 0); + + /* changes take effect */ + data = MT9V111S_OUTCTRL_CHIP_ENABLE | testpattern | 0x3000; + mt9v111_write_reg(MT9V111S_OUTPUT_CTRL, data); + } else { + testpattern = 0; + + data = mt9v111_read_reg(MT9V111S_ROW_NOISE_CTRL) | 0x40; + mt9v111_write_reg(MT9V111S_ROW_NOISE_CTRL, data); + + /* changes take effect */ + data = MT9V111S_OUTCTRL_CHIP_ENABLE | testpattern | 0x3000; + mt9v111_write_reg(MT9V111S_OUTPUT_CTRL, data); + } +} +#endif + + +/* --------------- IOCTL functions from v4l2_int_ioctl_desc --------------- */ + +/*! + * ioctl_g_ifparm - V4L2 sensor interface handler for vidioc_int_g_ifparm_num + * s: pointer to standard V4L2 device structure + * p: pointer to standard V4L2 vidioc_int_g_ifparm_num ioctl structure + * + * Gets slave interface parameters. + * Calculates the required xclk value to support the requested + * clock parameters in p. This value is returned in the p + * parameter. + * + * vidioc_int_g_ifparm returns platform-specific information about the + * interface settings used by the sensor. + * + * Given the image capture format in pix, the nominal frame period in + * timeperframe, calculate the required xclk frequency. + * + * Called on open. + */ +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + pr_debug("In mt9v111:ioctl_g_ifparm\n"); + + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + memset(p, 0, sizeof(*p)); + p->u.bt656.clock_curr = MT9V111_MCLK; + p->if_type = V4L2_IF_TYPE_BT656; + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.clock_min = MT9V111_CLK_MIN; + p->u.bt656.clock_max = MT9V111_CLK_MAX; + + return 0; +} + +/*! + * Sets the camera power. + * + * s pointer to the camera device + * on if 1, power is to be turned on. 0 means power is to be turned off + * + * ioctl_s_power - V4L2 sensor interface handler for vidioc_int_s_power_num + * @s: pointer to standard V4L2 device structure + * @on: power state to which device is to be set + * + * Sets devices power state to requrested state, if possible. + * This is called on suspend and resume. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + pr_debug("In mt9v111:ioctl_s_power\n"); + + sensor->on = on; + + if (on) + gpio_sensor_active(); + else + gpio_sensor_inactive(); + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + int ret = 0; + struct v4l2_captureparm *cparm = &a->parm.capture; + /* s->priv points to mt9v111_data */ + + pr_debug("In mt9v111:ioctl_g_parm\n"); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = mt9v111_data.streamcap.capability; + cparm->timeperframe = + mt9v111_data.streamcap.timeperframe; + cparm->capturemode = mt9v111_data.streamcap.capturemode; + ret = 0; + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_err(" type is not V4L2_BUF_TYPE_VIDEO_CAPTURE " \ + "but %d\n", a->type); + ret = -EINVAL; + break; + + default: + pr_err(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + int ret = 0; + struct v4l2_captureparm *cparm = &a->parm.capture; + /* s->priv points to mt9v111_data */ + + pr_debug("In mt9v111:ioctl_s_parm\n"); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + + /* Check that the new frame rate is allowed. + * Changing the frame rate is not allowed on this + *camera. */ + if (cparm->timeperframe.denominator != + mt9v111_data.streamcap.timeperframe.denominator) { + pr_err("ERROR: mt9v111: ioctl_s_parm: " \ + "This camera does not allow frame rate " + "changes.\n"); + ret = -EINVAL; + } else { + mt9v111_data.streamcap.timeperframe = + cparm->timeperframe; + /* Call any camera functions to match settings. */ + } + + /* Check that new capture mode is supported. */ + if ((cparm->capturemode != 0) && + !(cparm->capturemode & V4L2_MODE_HIGHQUALITY)) { + pr_err("ERROR: mt9v111: ioctl_s_parm: " \ + "unsupported capture mode\n"); + ret = -EINVAL; + } else { + mt9v111_data.streamcap.capturemode = + cparm->capturemode; + /* Call any camera functions to match settings. */ + /* Right now this camera only supports 1 mode. */ + } + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_err(" type is not V4L2_BUF_TYPE_VIDEO_CAPTURE " \ + "but %d\n", a->type); + ret = -EINVAL; + break; + + default: + pr_err(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return 0; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + /* s->priv points to mt9v111_data */ + + pr_debug("In mt9v111:ioctl_g_fmt_cap.\n"); + pr_debug(" Returning size of %dx%d\n", + sensor->pix.width, sensor->pix.height); + + f->fmt.pix = sensor->pix; + + return 0; +} + +/*! + * ioctl_queryctrl - V4L2 sensor interface handler for VIDIOC_QUERYCTRL ioctl + * @s: pointer to standard V4L2 device structure + * @qc: standard V4L2 VIDIOC_QUERYCTRL ioctl structure + * + * If the requested control is supported, returns the control information + * from the video_control[] array. Otherwise, returns -EINVAL if the + * control is not supported. + */ +static int ioctl_queryctrl(struct v4l2_int_device *s, struct v4l2_queryctrl *qc) +{ + pr_debug("In mt9v111:ioctl_queryctrl\n"); + + return 0; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + pr_debug("In mt9v111:ioctl_g_ctrl\n"); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + pr_debug(" V4L2_CID_BRIGHTNESS\n"); + vc->value = mt9v111_data.brightness; + break; + case V4L2_CID_CONTRAST: + pr_debug(" V4L2_CID_CONTRAST\n"); + vc->value = mt9v111_data.contrast; + break; + case V4L2_CID_SATURATION: + pr_debug(" V4L2_CID_SATURATION\n"); + vc->value = mt9v111_data.saturation; + break; + case V4L2_CID_HUE: + pr_debug(" V4L2_CID_HUE\n"); + vc->value = mt9v111_data.hue; + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + vc->value = 0; + break; + case V4L2_CID_DO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_DO_WHITE_BALANCE\n"); + vc->value = 0; + break; + case V4L2_CID_RED_BALANCE: + pr_debug(" V4L2_CID_RED_BALANCE\n"); + vc->value = mt9v111_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + pr_debug(" V4L2_CID_BLUE_BALANCE\n"); + vc->value = mt9v111_data.blue; + break; + case V4L2_CID_GAMMA: + pr_debug(" V4L2_CID_GAMMA\n"); + vc->value = 0; + break; + case V4L2_CID_EXPOSURE: + pr_debug(" V4L2_CID_EXPOSURE\n"); + vc->value = mt9v111_data.ae_mode; + break; + case V4L2_CID_AUTOGAIN: + pr_debug(" V4L2_CID_AUTOGAIN\n"); + vc->value = 0; + break; + case V4L2_CID_GAIN: + pr_debug(" V4L2_CID_GAIN\n"); + vc->value = 0; + break; + case V4L2_CID_HFLIP: + pr_debug(" V4L2_CID_HFLIP\n"); + vc->value = 0; + break; + case V4L2_CID_VFLIP: + pr_debug(" V4L2_CID_VFLIP\n"); + vc->value = 0; + break; + default: + pr_debug(" Default case\n"); + return -EPERM; + break; + } + + return 0; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + + pr_debug("In mt9v111:ioctl_s_ctrl %d\n", + vc->id); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + pr_debug(" V4L2_CID_BRIGHTNESS\n"); + break; + case V4L2_CID_CONTRAST: + pr_debug(" V4L2_CID_CONTRAST\n"); + break; + case V4L2_CID_SATURATION: + pr_debug(" V4L2_CID_SATURATION\n"); + retval = mt9v111_set_saturation(vc->value); + break; + case V4L2_CID_HUE: + pr_debug(" V4L2_CID_HUE\n"); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + pr_debug(" V4L2_CID_RED_BALANCE\n"); + break; + case V4L2_CID_BLUE_BALANCE: + pr_debug(" V4L2_CID_BLUE_BALANCE\n"); + break; + case V4L2_CID_GAMMA: + pr_debug(" V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + pr_debug(" V4L2_CID_EXPOSURE\n"); + retval = mt9v111_set_ae_mode(vc->value); + break; + case V4L2_CID_AUTOGAIN: + pr_debug(" V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + pr_debug(" V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + pr_debug(" V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + pr_debug(" V4L2_CID_VFLIP\n"); + break; + default: + pr_debug(" Default case\n"); + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + pr_debug("In mt9v111:ioctl_init\n"); + + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + uint32_t clock_rate = MT9V111_MCLK; + + pr_debug("In mt9v111:ioctl_dev_init\n"); + + gpio_sensor_active(); + + set_mclk_rate(&clock_rate); + mt9v111_rate_cal(&reset_frame_rate, clock_rate); + mt9v111_sensor_lib(mt9v111_device.coreReg, mt9v111_device.ifpReg); + + return 0; +} + +/*! + * This structure defines all the ioctls for this module and links them to the + * enumeration. + */ +static struct v4l2_int_ioctl_desc mt9v111_ioctl_desc[] = { + + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, + + /*! + * Delinitialise the dev. at slave detach. + * The complement of ioctl_dev_init. + */ +/* {vidioc_int_dev_exit_num, (v4l2_int_ioctl_func *) ioctl_dev_exit}, */ + + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *) ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *) ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *) ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *) ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *) ioctl_init}, + + /*! + * VIDIOC_ENUM_FMT ioctl for the CAPTURE buffer type. + */ +/* {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *) ioctl_enum_fmt_cap}, */ + + /*! + * VIDIOC_TRY_FMT ioctl for the CAPTURE buffer type. + * This ioctl is used to negotiate the image capture size and + * pixel format without actually making it take effect. + */ +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *) ioctl_try_fmt_cap}, */ + + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *) ioctl_g_fmt_cap}, + + /*! + * If the requested format is supported, configures the HW to use that + * format, returns error code if format not supported or HW can't be + * correctly configured. + */ +/* {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_s_fmt_cap}, */ + + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *) ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *) ioctl_s_parm}, +/* {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *) ioctl_queryctrl}, */ + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *) ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *) ioctl_s_ctrl}, +}; + +static struct v4l2_int_slave mt9v111_slave = { + .ioctls = mt9v111_ioctl_desc, + .num_ioctls = ARRAY_SIZE(mt9v111_ioctl_desc), +}; + +static struct v4l2_int_device mt9v111_int_device = { + .module = THIS_MODULE, + .name = "mt9v111", + .type = v4l2_int_type_slave, + .u = { + .slave = &mt9v111_slave, + }, +}; + +/*! + * mt9v111 I2C probe function + * Function set in i2c_driver struct. + * Called by insmod mt9v111_camera.ko. + * + * @return Error code indicating success or failure + */ +static int mt9v111_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + + pr_debug("In mt9v111_probe device id is %s\n", id->name); + + /* Set initial values for the sensor struct. */ + memset(&mt9v111_data, 0, sizeof(mt9v111_data)); + mt9v111_data.i2c_client = client; + pr_debug(" client name is %s\n", client->name); + mt9v111_data.pix.pixelformat = V4L2_PIX_FMT_UYVY; + mt9v111_data.pix.width = MT9V111_MAX_WIDTH; + mt9v111_data.pix.height = MT9V111_MAX_HEIGHT; + mt9v111_data.streamcap.capability = 0; /* No higher resolution or frame + * frame rate changes supported. + */ + mt9v111_data.streamcap.timeperframe.denominator = MT9V111_FRAME_RATE; + mt9v111_data.streamcap.timeperframe.numerator = 1; + + mt9v111_int_device.priv = &mt9v111_data; + + pr_debug(" type is %d (expect %d)\n", + mt9v111_int_device.type, v4l2_int_type_slave); + pr_debug(" num ioctls is %d\n", + mt9v111_int_device.u.slave->num_ioctls); + + /* This function attaches this structure to the /dev/video0 device. + * The pointer in priv points to the mt9v111_data structure here.*/ + retval = v4l2_int_device_register(&mt9v111_int_device); + + return retval; +} + +/*! + * Function set in i2c_driver struct. + * Called on rmmod mt9v111_camera.ko + */ +static int mt9v111_remove(struct i2c_client *client) +{ + pr_debug("In mt9v111_remove\n"); + + v4l2_int_device_unregister(&mt9v111_int_device); + return 0; +} + +/*! + * MT9V111 init function. + * Called by insmod mt9v111_camera.ko. + * + * @return Error code indicating success or failure + */ +static __init int mt9v111_init(void) +{ + u8 err; + + pr_debug("In mt9v111_init\n"); + + /* Allocate memory for state structures. */ + mt9v111_device.coreReg = (mt9v111_coreReg *) + kmalloc(sizeof(mt9v111_coreReg), GFP_KERNEL); + if (!mt9v111_device.coreReg) + return -1; + memset(mt9v111_device.coreReg, 0, sizeof(mt9v111_coreReg)); + + mt9v111_device.ifpReg = (mt9v111_IFPReg *) + kmalloc(sizeof(mt9v111_IFPReg), GFP_KERNEL); + if (!mt9v111_device.ifpReg) { + kfree(mt9v111_device.coreReg); + mt9v111_device.coreReg = NULL; + return -1; + } + memset(mt9v111_device.ifpReg, 0, sizeof(mt9v111_IFPReg)); + + /* Set contents of the just created structures. */ + mt9v111_config(); + + /* Tells the i2c driver what functions to call for this driver. */ + err = i2c_add_driver(&mt9v111_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * MT9V111 cleanup function. + * Called on rmmod mt9v111_camera.ko + * + * @return Error code indicating success or failure + */ +static void __exit mt9v111_clean(void) +{ + pr_debug("In mt9v111_clean()\n"); + + i2c_del_driver(&mt9v111_i2c_driver); + gpio_sensor_inactive(); + + if (mt9v111_device.coreReg) { + kfree(mt9v111_device.coreReg); + mt9v111_device.coreReg = NULL; + } + + if (mt9v111_device.ifpReg) { + kfree(mt9v111_device.ifpReg); + mt9v111_device.ifpReg = NULL; + } +} + +module_init(mt9v111_init); +module_exit(mt9v111_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Mt9v111 Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mt9v111.h b/drivers/media/video/mxc/capture/mt9v111.h new file mode 100644 index 000000000000..cf38cec4757c --- /dev/null +++ b/drivers/media/video/mxc/capture/mt9v111.h @@ -0,0 +1,431 @@ +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup Camera Sensor Drivers + */ + +/*! + * @file mt9v111.h + * + * @brief MT9V111 Camera Header file + * + * This header file contains defines and structures for the iMagic mi8012 + * aka the Micron mt9v111 camera. + * + * @ingroup Camera + */ + +#ifndef MT9V111_H_ +#define MT9V111_H_ + +/*! + * Basic camera values + */ +#define MT9V111_FRAME_RATE 30 +#define MT9V111_MCLK 27000000 /* Desired clock rate */ +#define MT9V111_CLK_MIN 12000000 /* This clock rate yields 15 fps */ +#define MT9V111_CLK_MAX 27000000 +#define MT9V111_MAX_WIDTH 640 /* Max width for this camera */ +#define MT9V111_MAX_HEIGHT 480 /* Max height for this camera */ + +/*! + * mt9v111 IFP REGISTER BANK MAP + */ +#define MT9V111I_ADDR_SPACE_SEL 0x1 +#define MT9V111I_BASE_MAXTRIX_SIGN 0x2 +#define MT9V111I_BASE_MAXTRIX_SCALE15 0x3 +#define MT9V111I_BASE_MAXTRIX_SCALE69 0x4 +#define MT9V111I_APERTURE_GAIN 0x5 +#define MT9V111I_MODE_CONTROL 0x6 +#define MT9V111I_SOFT_RESET 0x7 +#define MT9V111I_FORMAT_CONTROL 0x8 +#define MT9V111I_BASE_MATRIX_CFK1 0x9 +#define MT9V111I_BASE_MATRIX_CFK2 0xa +#define MT9V111I_BASE_MATRIX_CFK3 0xb +#define MT9V111I_BASE_MATRIX_CFK4 0xc +#define MT9V111I_BASE_MATRIX_CFK5 0xd +#define MT9V111I_BASE_MATRIX_CFK6 0xe +#define MT9V111I_BASE_MATRIX_CFK7 0xf +#define MT9V111I_BASE_MATRIX_CFK8 0x10 +#define MT9V111I_BASE_MATRIX_CFK9 0x11 +#define MT9V111I_AWB_POSITION 0x12 +#define MT9V111I_AWB_RED_GAIN 0x13 +#define MT9V111I_AWB_BLUE_GAIN 0x14 +#define MT9V111I_DELTA_MATRIX_CF_SIGN 0x15 +#define MT9V111I_DELTA_MATRIX_CF_D1 0x16 +#define MT9V111I_DELTA_MATRIX_CF_D2 0x17 +#define MT9V111I_DELTA_MATRIX_CF_D3 0x18 +#define MT9V111I_DELTA_MATRIX_CF_D4 0x19 +#define MT9V111I_DELTA_MATRIX_CF_D5 0x1a +#define MT9V111I_DELTA_MATRIX_CF_D6 0x1b +#define MT9V111I_DELTA_MATRIX_CF_D7 0x1c +#define MT9V111I_DELTA_MATRIX_CF_D8 0x1d +#define MT9V111I_DELTA_MATRIX_CF_D9 0x1e +#define MT9V111I_LUMINANCE_LIMIT_WB 0x20 +#define MT9V111I_RBG_MANUUAL_WB 0x21 +#define MT9V111I_AWB_RED_LIMIT 0x22 +#define MT9V111I_AWB_BLUE_LIMIT 0x23 +#define MT9V111I_MATRIX_ADJUST_LIMIT 0x24 +#define MT9V111I_AWB_SPEED 0x25 +#define MT9V111I_H_BOUND_AE 0x26 +#define MT9V111I_V_BOUND_AE 0x27 +#define MT9V111I_H_BOUND_AE_CEN_WIN 0x2b +#define MT9V111I_V_BOUND_AE_CEN_WIN 0x2c +#define MT9V111I_BOUND_AWB_WIN 0x2d +#define MT9V111I_AE_PRECISION_TARGET 0x2e +#define MT9V111I_AE_SPEED 0x2f +#define MT9V111I_RED_AWB_MEASURE 0x30 +#define MT9V111I_LUMA_AWB_MEASURE 0x31 +#define MT9V111I_BLUE_AWB_MEASURE 0x32 +#define MT9V111I_LIMIT_SHARP_SATU_CTRL 0x33 +#define MT9V111I_LUMA_OFFSET 0x34 +#define MT9V111I_CLIP_LIMIT_OUTPUT_LUMI 0x35 +#define MT9V111I_GAIN_LIMIT_AE 0x36 +#define MT9V111I_SHUTTER_WIDTH_LIMIT_AE 0x37 +#define MT9V111I_UPPER_SHUTTER_DELAY_LIM 0x39 +#define MT9V111I_OUTPUT_FORMAT_CTRL2 0x3a +#define MT9V111I_IPF_BLACK_LEVEL_SUB 0x3b +#define MT9V111I_IPF_BLACK_LEVEL_ADD 0x3c +#define MT9V111I_ADC_LIMIT_AE_ADJ 0x3d +#define MT9V111I_GAIN_THRE_CCAM_ADJ 0x3e +#define MT9V111I_LINEAR_AE 0x3f +#define MT9V111I_THRESHOLD_EDGE_DEFECT 0x47 +#define MT9V111I_LUMA_SUM_MEASURE 0x4c +#define MT9V111I_TIME_ADV_SUM_LUMA 0x4d +#define MT9V111I_MOTION 0x52 +#define MT9V111I_GAMMA_KNEE_Y12 0x53 +#define MT9V111I_GAMMA_KNEE_Y34 0x54 +#define MT9V111I_GAMMA_KNEE_Y56 0x55 +#define MT9V111I_GAMMA_KNEE_Y78 0x56 +#define MT9V111I_GAMMA_KNEE_Y90 0x57 +#define MT9V111I_GAMMA_VALUE_Y0 0x58 +#define MT9V111I_SHUTTER_60 0x59 +#define MT9V111I_SEARCH_FLICK_60 0x5c +#define MT9V111I_RATIO_IMAGE_GAIN_BASE 0x5e +#define MT9V111I_RATIO_IMAGE_GAIN_DELTA 0x5f +#define MT9V111I_SIGN_VALUE_REG5F 0x60 +#define MT9V111I_AE_GAIN 0x62 +#define MT9V111I_MAX_GAIN_AE 0x67 +#define MT9V111I_LENS_CORRECT_CTRL 0x80 +#define MT9V111I_SHADING_PARAMETER1 0x81 +#define MT9V111I_SHADING_PARAMETER2 0x82 +#define MT9V111I_SHADING_PARAMETER3 0x83 +#define MT9V111I_SHADING_PARAMETER4 0x84 +#define MT9V111I_SHADING_PARAMETER5 0x85 +#define MT9V111I_SHADING_PARAMETER6 0x86 +#define MT9V111I_SHADING_PARAMETER7 0x87 +#define MT9V111I_SHADING_PARAMETER8 0x88 +#define MT9V111I_SHADING_PARAMETER9 0x89 +#define MT9V111I_SHADING_PARAMETER10 0x8A +#define MT9V111I_SHADING_PARAMETER11 0x8B +#define MT9V111I_SHADING_PARAMETER12 0x8C +#define MT9V111I_SHADING_PARAMETER13 0x8D +#define MT9V111I_SHADING_PARAMETER14 0x8E +#define MT9V111I_SHADING_PARAMETER15 0x8F +#define MT9V111I_SHADING_PARAMETER16 0x90 +#define MT9V111I_SHADING_PARAMETER17 0x91 +#define MT9V111I_SHADING_PARAMETER18 0x92 +#define MT9V111I_SHADING_PARAMETER19 0x93 +#define MT9V111I_SHADING_PARAMETER20 0x94 +#define MT9V111I_SHADING_PARAMETER21 0x95 +#define MT9V111i_FLASH_CTRL 0x98 +#define MT9V111i_LINE_COUNTER 0x99 +#define MT9V111i_FRAME_COUNTER 0x9A +#define MT9V111i_H_PAN 0xA5 +#define MT9V111i_H_ZOOM 0xA6 +#define MT9V111i_H_SIZE 0xA7 +#define MT9V111i_V_PAN 0xA8 +#define MT9V111i_V_ZOOM 0xA9 +#define MT9V111i_V_SIZE 0xAA + +#define MT9V111I_SEL_IFP 0x1 +#define MT9V111I_SEL_SCA 0x4 +#define MT9V111I_FC_RGB_OR_YUV 0x1000 + +/*! + * Mt9v111 SENSOR CORE REGISTER BANK MAP + */ +#define MT9V111S_ADDR_SPACE_SEL 0x1 +#define MT9V111S_COLUMN_START 0x2 +#define MT9V111S_WIN_HEIGHT 0x3 +#define MT9V111S_WIN_WIDTH 0x4 +#define MT9V111S_HOR_BLANKING 0x5 +#define MT9V111S_VER_BLANKING 0x6 +#define MT9V111S_OUTPUT_CTRL 0x7 +#define MT9V111S_ROW_START 0x8 +#define MT9V111S_SHUTTER_WIDTH 0x9 +#define MT9V111S_PIXEL_CLOCK_SPEED 0xa +#define MT9V111S_RESTART 0xb +#define MT9V111S_SHUTTER_DELAY 0xc +#define MT9V111S_RESET 0xd +#define MT9V111S_COLUMN_START_IN_ZOOM 0x12 +#define MT9V111S_ROW_START_IN_ZOOM 0x13 +#define MT9V111S_DIGITAL_ZOOM 0x1e +#define MT9V111S_READ_MODE 0x20 +#define MT9V111S_DAC_CTRL 0x27 +#define MT9V111S_GREEN1_GAIN 0x2b +#define MT9V111S_BLUE_GAIN 0x2c +#define MT9V111S_READ_GAIN 0x2d +#define MT9V111S_GREEN2_GAIN 0x2e +#define MT9V111S_ROW_NOISE_CTRL 0x30 +#define MT9V111S_DARK_TARGET_W 0x31 +#define MT9V111S_TEST_DATA 0x32 +#define MT9V111S_GLOBAL_GAIN 0x35 +#define MT9V111S_SENSOR_CORE_VERSION 0x36 +#define MT9V111S_DARK_TARGET_WO 0x37 +#define MT9V111S_VERF_DAC 0x41 +#define MT9V111S_VCM_VCL 0x42 +#define MT9V111S_DISABLE_BYPASS 0x58 +#define MT9V111S_CALIB_MEAN_TEST 0x59 +#define MT9V111S_DARK_G1_AVE 0x5B +#define MT9V111S_DARK_G2_AVE 0x5C +#define MT9V111S_DARK_R_AVE 0x5D +#define MT9V111S_DARK_B_AVE 0x5E +#define MT9V111S_CAL_THRESHOLD 0x5f +#define MT9V111S_CAL_G1 0x60 +#define MT9V111S_CAL_G2 0x61 +#define MT9V111S_CAL_CTRL 0x62 +#define MT9V111S_CAL_R 0x63 +#define MT9V111S_CAL_B 0x64 +#define MT9V111S_CHIP_ENABLE 0xF1 +#define MT9V111S_CHIP_VERSION 0xFF + +/* OUTPUT_CTRL */ +#define MT9V111S_OUTCTRL_SYNC 0x1 +#define MT9V111S_OUTCTRL_CHIP_ENABLE 0x2 +#define MT9V111S_OUTCTRL_TEST_MODE 0x40 + +/* READ_MODE */ +#define MT9V111S_RM_NOBADFRAME 0x1 +#define MT9V111S_RM_NODESTRUCT 0x2 +#define MT9V111S_RM_COLUMNSKIP 0x4 +#define MT9V111S_RM_ROWSKIP 0x8 +#define MT9V111S_RM_BOOSTEDRESET 0x1000 +#define MT9V111S_RM_COLUMN_LATE 0x10 +#define MT9V111S_RM_ROW_LATE 0x80 +#define MT9V111S_RM_RIGTH_TO_LEFT 0x4000 +#define MT9V111S_RM_BOTTOM_TO_TOP 0x8000 + +/*! I2C Slave Address */ +#define MT9V111_I2C_ADDRESS 0x48 + +/*! + * The image resolution enum for the mt9v111 sensor + */ +typedef enum { + MT9V111_OutputResolution_VGA = 0, /*!< VGA size */ + MT9V111_OutputResolution_QVGA, /*!< QVGA size */ + MT9V111_OutputResolution_CIF, /*!< CIF size */ + MT9V111_OutputResolution_QCIF, /*!< QCIF size */ + MT9V111_OutputResolution_QQVGA, /*!< QQVGA size */ + MT9V111_OutputResolution_SXGA /*!< SXGA size */ +} MT9V111_OutputResolution; + +enum { + MT9V111_WINWIDTH = 0x287, + MT9V111_WINWIDTH_DEFAULT = 0x287, + MT9V111_WINWIDTH_MIN = 0x9, + + MT9V111_WINHEIGHT = 0x1E7, + MT9V111_WINHEIGHT_DEFAULT = 0x1E7, + + MT9V111_HORZBLANK_DEFAULT = 0x26, + MT9V111_HORZBLANK_MIN = 0x9, + MT9V111_HORZBLANK_MAX = 0x3FF, + + MT9V111_VERTBLANK_DEFAULT = 0x4, + MT9V111_VERTBLANK_MIN = 0x3, + MT9V111_VERTBLANK_MAX = 0xFFF, +}; + +/*! + * Mt9v111 Core Register structure. + */ +typedef struct { + u32 addressSelect; /*!< select address bank for Core Register 0x4 */ + u32 columnStart; /*!< Starting Column */ + u32 windowHeight; /*!< Window Height */ + u32 windowWidth; /*!< Window Width */ + u32 horizontalBlanking; /*!< Horizontal Blank time, in pixels */ + u32 verticalBlanking; /*!< Vertical Blank time, in pixels */ + u32 outputControl; /*!< Register to control sensor output */ + u32 rowStart; /*!< Starting Row */ + u32 shutterWidth; + u32 pixelClockSpeed; /*!< pixel date rate */ + u32 restart; /*!< Abandon the readout of current frame */ + u32 shutterDelay; + u32 reset; /*!< reset the sensor to the default mode */ + u32 zoomColStart; /*!< Column start in the Zoom mode */ + u32 zomRowStart; /*!< Row start in the Zoom mode */ + u32 digitalZoom; /*!< 1 means zoom by 2 */ + u32 readMode; /*!< Readmode: aspects of the readout of the sensor */ + u32 dACStandbyControl; + u32 green1Gain; /*!< Gain Settings */ + u32 blueGain; + u32 redGain; + u32 green2Gain; + u32 rowNoiseControl; + u32 darkTargetwNC; + u32 testData; /*!< test mode */ + u32 globalGain; + u32 chipVersion; + u32 darkTargetwoNC; + u32 vREFDACs; + u32 vCMandVCL; + u32 disableBypass; + u32 calibMeanTest; + u32 darkG1average; + u32 darkG2average; + u32 darkRaverage; + u32 darkBaverage; + u32 calibThreshold; + u32 calibGreen1; + u32 calibGreen2; + u32 calibControl; + u32 calibRed; + u32 calibBlue; + u32 chipEnable; /*!< Image core Registers written by image flow processor */ +} mt9v111_coreReg; + +/*! + * Mt9v111 IFP Register structure. + */ +typedef struct { + u32 addrSpaceSel; /*!< select address bank for Core Register 0x1 */ + u32 baseMaxtrixSign; /*!< sign of coefficient for base color correction matrix */ + u32 baseMaxtrixScale15; /*!< scaling of color correction coefficient K1-5 */ + u32 baseMaxtrixScale69; /*!< scaling of color correction coefficient K6-9 */ + u32 apertureGain; /*!< sharpening */ + u32 modeControl; /*!< bit 7 CCIR656 sync codes are embedded in the image */ + u32 softReset; /*!< Image processing mode: 1 reset mode, 0 operational mode */ + u32 formatControl; /*!< bit12 1 for RGB565, 0 for YcrCb */ + u32 baseMatrixCfk1; /*!< K1 Color correction coefficient */ + u32 baseMatrixCfk2; /*!< K2 Color correction coefficient */ + u32 baseMatrixCfk3; /*!< K3 Color correction coefficient */ + u32 baseMatrixCfk4; /*!< K4 Color correction coefficient */ + u32 baseMatrixCfk5; /*!< K5 Color correction coefficient */ + u32 baseMatrixCfk6; /*!< K6 Color correction coefficient */ + u32 baseMatrixCfk7; /*!< K7 Color correction coefficient */ + u32 baseMatrixCfk8; /*!< K8 Color correction coefficient */ + u32 baseMatrixCfk9; /*!< K9 Color correction coefficient */ + u32 awbPosition; /*!< Current position of AWB color correction matrix */ + u32 awbRedGain; /*!< Current value of AWB red channel gain */ + u32 awbBlueGain; /*!< Current value of AWB blue channel gain */ + u32 deltaMatrixCFSign; /*!< Sign of coefficients of delta color correction matrix register */ + u32 deltaMatrixCFD1; /*!< D1 Delta coefficient */ + u32 deltaMatrixCFD2; /*!< D2 Delta coefficient */ + u32 deltaMatrixCFD3; /*!< D3 Delta coefficient */ + u32 deltaMatrixCFD4; /*!< D4 Delta coefficient */ + u32 deltaMatrixCFD5; /*!< D5 Delta coefficient */ + u32 deltaMatrixCFD6; /*!< D6 Delta coefficient */ + u32 deltaMatrixCFD7; /*!< D7 Delta coefficient */ + u32 deltaMatrixCFD8; /*!< D8 Delta coefficient */ + u32 deltaMatrixCFD9; /*!< D9 Delta coefficient */ + u32 lumLimitWB; /*!< Luminance range of pixels considered in WB statistics */ + u32 RBGManualWB; /*!< Red and Blue color channel gains for manual white balance */ + u32 awbRedLimit; /*!< Limits on Red channel gain adjustment through AWB */ + u32 awbBlueLimit; /*!< Limits on Blue channel gain adjustment through AWB */ + u32 matrixAdjLimit; /*!< Limits on color correction matrix adjustment through AWB */ + u32 awbSpeed; /*!< AWB speed and color saturation control */ + u32 HBoundAE; /*!< Horizontal boundaries of AWB measurement window */ + u32 VBoundAE; /*!< Vertical boundaries of AWB measurement window */ + u32 HBoundAECenWin; /*!< Horizontal boundaries of AE measurement window for backlight compensation */ + u32 VBoundAECenWin; /*!< Vertical boundaries of AE measurement window for backlight compensation */ + u32 boundAwbWin; /*!< Boundaries of AWB measurement window */ + u32 AEPrecisionTarget; /*!< Auto exposure target and precision control */ + u32 AESpeed; /*!< AE speed and sensitivity control register */ + u32 redAWBMeasure; /*!< Measure of the red channel value used by AWB */ + u32 lumaAWBMeasure; /*!< Measure of the luminance channel value used by AWB */ + u32 blueAWBMeasure; /*!< Measure of the blue channel value used by AWB */ + u32 limitSharpSatuCtrl; /*!< Automatic control of sharpness and color saturation */ + u32 lumaOffset; /*!< Luminance offset control (brightness control) */ + u32 clipLimitOutputLumi; /*!< Clipping limits for output luminance */ + u32 gainLimitAE; /*!< Imager gain limits for AE adjustment */ + u32 shutterWidthLimitAE; /*!< Shutter width (exposure time) limits for AE adjustment */ + u32 upperShutterDelayLi; /*!< Upper Shutter Delay Limit */ + u32 outputFormatCtrl2; /*!< Output Format Control 2 + 00 = 16-bit RGB565. + 01 = 15-bit RGB555. + 10 = 12-bit RGB444x. + 11 = 12-bit RGBx444. */ + u32 ipfBlackLevelSub; /*!< IFP black level subtraction */ + u32 ipfBlackLevelAdd; /*!< IFP black level addition */ + u32 adcLimitAEAdj; /*!< ADC limits for AE adjustment */ + u32 agimnThreCamAdj; /*!< Gain threshold for CCM adjustment */ + u32 linearAE; + u32 thresholdEdgeDefect; /*!< Edge threshold for interpolation and defect correction */ + u32 lumaSumMeasure; /*!< Luma measured by AE engine */ + u32 timeAdvSumLuma; /*!< Time-averaged luminance value tracked by auto exposure */ + u32 motion; /*!< 1 when motion is detected */ + u32 gammaKneeY12; /*!< Gamma knee points Y1 and Y2 */ + u32 gammaKneeY34; /*!< Gamma knee points Y3 and Y4 */ + u32 gammaKneeY56; /*!< Gamma knee points Y5 and Y6 */ + u32 gammaKneeY78; /*!< Gamma knee points Y7 and Y8 */ + u32 gammaKneeY90; /*!< Gamma knee points Y9 and Y10 */ + u32 gammaKneeY0; /*!< Gamma knee point Y0 */ + u32 shutter_width_60; + u32 search_flicker_60; + u32 ratioImageGainBase; + u32 ratioImageGainDelta; + u32 signValueReg5F; + u32 aeGain; + u32 maxGainAE; + u32 lensCorrectCtrl; + u32 shadingParameter1; /*!< Shade Parameters */ + u32 shadingParameter2; + u32 shadingParameter3; + u32 shadingParameter4; + u32 shadingParameter5; + u32 shadingParameter6; + u32 shadingParameter7; + u32 shadingParameter8; + u32 shadingParameter9; + u32 shadingParameter10; + u32 shadingParameter11; + u32 shadingParameter12; + u32 shadingParameter13; + u32 shadingParameter14; + u32 shadingParameter15; + u32 shadingParameter16; + u32 shadingParameter17; + u32 shadingParameter18; + u32 shadingParameter19; + u32 shadingParameter20; + u32 shadingParameter21; + u32 flashCtrl; /*!< Flash control */ + u32 lineCounter; /*!< Line counter */ + u32 frameCounter; /*!< Frame counter */ + u32 HPan; /*!< Horizontal pan in decimation */ + u32 HZoom; /*!< Horizontal zoom in decimation */ + u32 HSize; /*!< Horizontal output size iIn decimation */ + u32 VPan; /*!< Vertical pan in decimation */ + u32 VZoom; /*!< Vertical zoom in decimation */ + u32 VSize; /*!< Vertical output size in decimation */ +} mt9v111_IFPReg; + +/*! + * mt9v111 Config structure + */ +typedef struct { + mt9v111_coreReg *coreReg; /*!< Sensor Core Register Bank */ + mt9v111_IFPReg *ifpReg; /*!< IFP Register Bank */ +} mt9v111_conf; + +typedef struct { + u8 index; + u16 width; + u16 height; +} mt9v111_image_format; + +#endif /* MT9V111_H_ */ diff --git a/drivers/media/video/mxc/capture/mx27_csi.c b/drivers/media/video/mxc/capture/mx27_csi.c new file mode 100644 index 000000000000..24fce05be110 --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_csi.c @@ -0,0 +1,333 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_csi.c + * + * @brief CMOS Sensor interface functions + * + * @ingroup CSI + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <mach/clock.h> +#include <mach/hardware.h> + +#include "mx27_csi.h" + +static csi_config_t g_csi_cfg; /* csi hardware configuration */ +static bool gcsi_mclk_on = false; +static csi_irq_callback_t g_callback = 0; +static void *g_callback_data = 0; +static struct clk csi_mclk; + +static irqreturn_t csi_irq_handler(int irq, void *data) +{ + unsigned long status = __raw_readl(CSI_CSISR); + + __raw_writel(status, CSI_CSISR); + if (g_callback) + g_callback(g_callback_data, status); + + pr_debug("CSI status = 0x%08lX\n", status); + + return IRQ_HANDLED; +} + +static void csihw_set_config(csi_config_t * cfg) +{ + unsigned val = 0; + + /* control reg 1 */ + val |= cfg->swap16_en ? BIT_SWAP16_EN : 0; + val |= cfg->ext_vsync ? BIT_EXT_VSYNC : 0; + val |= cfg->eof_int_en ? BIT_EOF_INT_EN : 0; + val |= cfg->prp_if_en ? BIT_PRP_IF_EN : 0; + val |= cfg->ccir_mode ? BIT_CCIR_MODE : 0; + val |= cfg->cof_int_en ? BIT_COF_INT_EN : 0; + val |= cfg->sf_or_inten ? BIT_SF_OR_INTEN : 0; + val |= cfg->rf_or_inten ? BIT_RF_OR_INTEN : 0; + val |= cfg->statff_level << SHIFT_STATFF_LEVEL; + val |= cfg->staff_inten ? BIT_STATFF_INTEN : 0; + val |= cfg->rxff_level << SHIFT_RXFF_LEVEL; + val |= cfg->rxff_inten ? BIT_RXFF_INTEN : 0; + val |= cfg->sof_pol ? BIT_SOF_POL : 0; + val |= cfg->sof_inten ? BIT_SOF_INTEN : 0; + val |= cfg->mclkdiv << SHIFT_MCLKDIV; + val |= cfg->hsync_pol ? BIT_HSYNC_POL : 0; + val |= cfg->ccir_en ? BIT_CCIR_EN : 0; + val |= cfg->mclken ? BIT_MCLKEN : 0; + val |= cfg->fcc ? BIT_FCC : 0; + val |= cfg->pack_dir ? BIT_PACK_DIR : 0; + val |= cfg->gclk_mode ? BIT_GCLK_MODE : 0; + val |= cfg->inv_data ? BIT_INV_DATA : 0; + val |= cfg->inv_pclk ? BIT_INV_PCLK : 0; + val |= cfg->redge ? BIT_REDGE : 0; + + __raw_writel(val, CSI_CSICR1); + + /* control reg 3 */ + val = 0x0; + val |= cfg->csi_sup ? BIT_CSI_SUP : 0; + val |= cfg->zero_pack_en ? BIT_ZERO_PACK_EN : 0; + val |= cfg->ecc_int_en ? BIT_ECC_INT_EN : 0; + val |= cfg->ecc_auto_en ? BIT_ECC_AUTO_EN : 0; + + __raw_writel(val, CSI_CSICR3); + + /* rxfifo counter */ + __raw_writel(cfg->rxcnt, CSI_CSIRXCNT); + + /* update global config */ + memcpy(&g_csi_cfg, cfg, sizeof(csi_config_t)); +} + +static void csihw_reset_frame_count(void) +{ + __raw_writel(__raw_readl(CSI_CSICR3) | BIT_FRMCNT_RST, CSI_CSICR3); +} + +static void csihw_reset(void) +{ + csihw_reset_frame_count(); + __raw_writel(CSICR1_RESET_VAL, CSI_CSICR1); + __raw_writel(CSICR2_RESET_VAL, CSI_CSICR2); + __raw_writel(CSICR3_RESET_VAL, CSI_CSICR3); +} + +/*! + * csi_init_interface + * Sets initial values for the CSI registers. + * The width and height of the sensor and the actual frame size will be + * set to the same values. + * @param width Sensor width + * @param height Sensor height + * @param pixel_fmt pixel format + * @param sig csi_signal_cfg_t + * + * @return 0 for success, -EINVAL for error + */ +int32_t csi_init_interface(uint16_t width, uint16_t height, + uint32_t pixel_fmt, csi_signal_cfg_t sig) +{ + csi_config_t cfg; + + /* Set the CSI_SENS_CONF register remaining fields */ + cfg.swap16_en = 1; + cfg.ext_vsync = sig.ext_vsync; + cfg.eof_int_en = 0; + cfg.prp_if_en = 1; + cfg.ccir_mode = 0; + cfg.cof_int_en = 0; + cfg.sf_or_inten = 0; + cfg.rf_or_inten = 0; + cfg.statff_level = 0; + cfg.staff_inten = 0; + cfg.rxff_level = 2; + cfg.rxff_inten = 0; + cfg.sof_pol = 1; + cfg.sof_inten = 0; + cfg.mclkdiv = 0; + cfg.hsync_pol = 1; + cfg.ccir_en = 0; + cfg.mclken = gcsi_mclk_on ? 1 : 0; + cfg.fcc = 1; + cfg.pack_dir = 0; + cfg.gclk_mode = 1; + cfg.inv_data = sig.data_pol; + cfg.inv_pclk = sig.pixclk_pol; + cfg.redge = 1; + cfg.csicnt1_rsv = 0; + + /* control reg 3 */ + cfg.frmcnt = 0; + cfg.frame_reset = 0; + cfg.csi_sup = 0; + cfg.zero_pack_en = 0; + cfg.ecc_int_en = 0; + cfg.ecc_auto_en = 0; + + csihw_set_config(&cfg); + + return 0; +} + +/*! + * csi_enable_prpif + * Enable or disable CSI-PrP interface + * @param enable Non-zero to enable, zero to disable + */ +void csi_enable_prpif(uint32_t enable) +{ + if (enable) { + g_csi_cfg.prp_if_en = 1; + g_csi_cfg.sof_inten = 0; + g_csi_cfg.pack_dir = 0; + } else { + g_csi_cfg.prp_if_en = 0; + g_csi_cfg.sof_inten = 1; + g_csi_cfg.pack_dir = 1; + } + + csihw_set_config(&g_csi_cfg); +} + +/*! + * csi_enable_mclk + * + * @param src enum define which source to control the clk + * CSI_MCLK_VF CSI_MCLK_ENC CSI_MCLK_RAW CSI_MCLK_I2C + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return 0 for success + */ +int32_t csi_enable_mclk(int src, bool flag, bool wait) +{ + if (flag == true) { + clk_enable(&csi_mclk); + if (wait == true) + msleep(10); + pr_debug("Enable csi clock from source %d\n", src); + gcsi_mclk_on = true; + } else { + clk_disable(&csi_mclk); + pr_debug("Disable csi clock from source %d\n", src); + gcsi_mclk_on = false; + } + + return 0; +} + +/*! + * csi_read_mclk_flag + * + * @return gcsi_mclk_source + */ +int csi_read_mclk_flag(void) +{ + return 0; +} + +void csi_set_callback(csi_irq_callback_t callback, void *data) +{ + g_callback = callback; + g_callback_data = data; +} + +static void _mclk_recalc(struct clk *clk) +{ + u32 div; + + div = (__raw_readl(CSI_CSICR1) & BIT_MCLKDIV) >> SHIFT_MCLKDIV; + div = (div + 1) * 2; + + clk->rate = clk->parent->rate / div; +} + +static unsigned long _mclk_round_rate(struct clk *clk, unsigned long rate) +{ + /* Keep CSI divider and change parent clock */ + if (clk->parent->round_rate) { + return clk->parent->round_rate(clk->parent, rate * 2); + } + return 0; +} + +static int _mclk_set_rate(struct clk *clk, unsigned long rate) +{ + int ret = -EINVAL; + + /* Keep CSI divider and change parent clock */ + if (clk->parent->set_rate) { + ret = clk->parent->set_rate(clk->parent, rate * 2); + if (ret == 0) { + clk->rate = clk->parent->rate / 2; + } + } + + return ret; +} + +static int _mclk_enable(struct clk *clk) +{ + __raw_writel(__raw_readl(CSI_CSICR1) | BIT_MCLKEN, CSI_CSICR1); + return 0; +} + +static void _mclk_disable(struct clk *clk) +{ + __raw_writel(__raw_readl(CSI_CSICR1) & ~BIT_MCLKEN, CSI_CSICR1); +} + +static struct clk csi_mclk = { + .name = "csi_clk", + .recalc = _mclk_recalc, + .round_rate = _mclk_round_rate, + .set_rate = _mclk_set_rate, + .enable = _mclk_enable, + .disable = _mclk_disable, +}; + +int32_t __init csi_init_module(void) +{ + int ret = 0; + struct clk *per_clk; + + per_clk = clk_get(NULL, "csi_perclk"); + if (IS_ERR(per_clk)) + return PTR_ERR(per_clk); + clk_put(per_clk); + csi_mclk.parent = per_clk; + clk_register(&csi_mclk); + clk_enable(per_clk); + csi_mclk.recalc(&csi_mclk); + + csihw_reset(); + + /* interrupt enable */ + ret = request_irq(MXC_INT_CSI, csi_irq_handler, 0, "csi", 0); + if (ret) + pr_debug("CSI error: irq request fail\n"); + + return ret; +} + +void __exit csi_cleanup_module(void) +{ + /* free irq */ + free_irq(MXC_INT_CSI, 0); + + clk_disable(&csi_mclk); +} + +module_init(csi_init_module); +module_exit(csi_cleanup_module); + +EXPORT_SYMBOL(csi_init_interface); +EXPORT_SYMBOL(csi_enable_mclk); +EXPORT_SYMBOL(csi_read_mclk_flag); +EXPORT_SYMBOL(csi_set_callback); +EXPORT_SYMBOL(csi_enable_prpif); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MX27 CSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/mx27_csi.h b/drivers/media/video/mxc/capture/mx27_csi.h new file mode 100644 index 000000000000..9bd99781e626 --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_csi.h @@ -0,0 +1,167 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_csi.h + * + * @brief CMOS Sensor interface functions + * + * @ingroup CSI + */ + +#ifndef MX27_CSI_H +#define MX27_CSI_H + +#include <linux/io.h> + +/* reset values */ +#define CSICR1_RESET_VAL 0x40000800 +#define CSICR2_RESET_VAL 0x0 +#define CSICR3_RESET_VAL 0x0 + +/* csi control reg 1 */ +#define BIT_SWAP16_EN (0x1 << 31) +#define BIT_EXT_VSYNC (0x1 << 30) +#define BIT_EOF_INT_EN (0x1 << 29) +#define BIT_PRP_IF_EN (0x1 << 28) +#define BIT_CCIR_MODE (0x1 << 27) +#define BIT_COF_INT_EN (0x1 << 26) +#define BIT_SF_OR_INTEN (0x1 << 25) +#define BIT_RF_OR_INTEN (0x1 << 24) +#define BIT_STATFF_LEVEL (0x3 << 22) +#define BIT_STATFF_INTEN (0x1 << 21) +#define BIT_RXFF_LEVEL (0x3 << 19) +#define BIT_RXFF_INTEN (0x1 << 18) +#define BIT_SOF_POL (0x1 << 17) +#define BIT_SOF_INTEN (0x1 << 16) +#define BIT_MCLKDIV (0xF << 12) +#define BIT_HSYNC_POL (0x1 << 11) +#define BIT_CCIR_EN (0x1 << 10) +#define BIT_MCLKEN (0x1 << 9) +#define BIT_FCC (0x1 << 8) +#define BIT_PACK_DIR (0x1 << 7) +#define BIT_CLR_STATFIFO (0x1 << 6) +#define BIT_CLR_RXFIFO (0x1 << 5) +#define BIT_GCLK_MODE (0x1 << 4) +#define BIT_INV_DATA (0x1 << 3) +#define BIT_INV_PCLK (0x1 << 2) +#define BIT_REDGE (0x1 << 1) + +#define SHIFT_STATFF_LEVEL 22 +#define SHIFT_RXFF_LEVEL 19 +#define SHIFT_MCLKDIV 12 + +/* control reg 3 */ +#define BIT_FRMCNT (0xFFFF << 16) +#define BIT_FRMCNT_RST (0x1 << 15) +#define BIT_CSI_SUP (0x1 << 3) +#define BIT_ZERO_PACK_EN (0x1 << 2) +#define BIT_ECC_INT_EN (0x1 << 1) +#define BIT_ECC_AUTO_EN (0x1) + +#define SHIFT_FRMCNT 16 + +/* csi status reg */ +#define BIT_SFF_OR_INT (0x1 << 25) +#define BIT_RFF_OR_INT (0x1 << 24) +#define BIT_STATFF_INT (0x1 << 21) +#define BIT_RXFF_INT (0x1 << 18) +#define BIT_EOF_INT (0x1 << 17) +#define BIT_SOF_INT (0x1 << 16) +#define BIT_F2_INT (0x1 << 15) +#define BIT_F1_INT (0x1 << 14) +#define BIT_COF_INT (0x1 << 13) +#define BIT_ECC_INT (0x1 << 1) +#define BIT_DRDY (0x1 << 0) + +#define CSI_MCLK_VF 1 +#define CSI_MCLK_ENC 2 +#define CSI_MCLK_RAW 4 +#define CSI_MCLK_I2C 8 + +#define CSI_CSICR1 (IO_ADDRESS(CSI_BASE_ADDR)) +#define CSI_CSICR2 (IO_ADDRESS(CSI_BASE_ADDR + 0x4)) +#define CSI_CSISR (IO_ADDRESS(CSI_BASE_ADDR + 0x8)) +#define CSI_STATFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0xC)) +#define CSI_CSIRXFIFO (IO_ADDRESS(CSI_BASE_ADDR + 0x10)) +#define CSI_CSIRXCNT (IO_ADDRESS(CSI_BASE_ADDR + 0x14)) +#define CSI_CSICR3 (IO_ADDRESS(CSI_BASE_ADDR + 0x1C)) + +#define CSI_CSIRXFIFO_PHYADDR (CSI_BASE_ADDR + 0x10) + +static __inline void csi_clear_status(unsigned long status) +{ + __raw_writel(status, CSI_CSISR); +} + +typedef struct { + unsigned data_width:3; + unsigned clk_mode:2; + unsigned ext_vsync:1; + unsigned Vsync_pol:1; + unsigned Hsync_pol:1; + unsigned pixclk_pol:1; + unsigned data_pol:1; + unsigned sens_clksrc:1; +} csi_signal_cfg_t; + +typedef struct { + /* control reg 1 */ + unsigned int swap16_en:1; + unsigned int ext_vsync:1; + unsigned int eof_int_en:1; + unsigned int prp_if_en:1; + unsigned int ccir_mode:1; + unsigned int cof_int_en:1; + unsigned int sf_or_inten:1; + unsigned int rf_or_inten:1; + unsigned int statff_level:2; + unsigned int staff_inten:1; + unsigned int rxff_level:2; + unsigned int rxff_inten:1; + unsigned int sof_pol:1; + unsigned int sof_inten:1; + unsigned int mclkdiv:4; + unsigned int hsync_pol:1; + unsigned int ccir_en:1; + unsigned int mclken:1; + unsigned int fcc:1; + unsigned int pack_dir:1; + unsigned int gclk_mode:1; + unsigned int inv_data:1; + unsigned int inv_pclk:1; + unsigned int redge:1; + unsigned int csicnt1_rsv:1; + + /* control reg 3 */ + unsigned int frmcnt:16; + unsigned int frame_reset:1; + unsigned int csi_sup:1; + unsigned int zero_pack_en:1; + unsigned int ecc_int_en:1; + unsigned int ecc_auto_en:1; + + /* fifo counter */ + unsigned int rxcnt; +} csi_config_t; + +typedef void (*csi_irq_callback_t) (void *data, unsigned long status); + +int32_t csi_enable_mclk(int src, bool flag, bool wait); +int32_t csi_init_interface(uint16_t width, uint16_t height, + uint32_t pixel_fmt, csi_signal_cfg_t sig); +int csi_read_mclk_flag(void); +void csi_set_callback(csi_irq_callback_t callback, void *data); +void csi_enable_prpif(uint32_t enable); + +#endif diff --git a/drivers/media/video/mxc/capture/mx27_prp.h b/drivers/media/video/mxc/capture/mx27_prp.h new file mode 100644 index 000000000000..e32e9029daff --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_prp.h @@ -0,0 +1,310 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_prp.h + * + * @brief Header file for MX27 V4L2 capture driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#ifndef __MX27_PRP_H__ +#define __MX27_PRP_H__ + +#define PRP_REG(ofs) (IO_ADDRESS(EMMA_BASE_ADDR) + ofs) + +/* Register definitions of PrP */ +#define PRP_CNTL PRP_REG(0x00) +#define PRP_INTRCNTL PRP_REG(0x04) +#define PRP_INTRSTATUS PRP_REG(0x08) +#define PRP_SOURCE_Y_PTR PRP_REG(0x0C) +#define PRP_SOURCE_CB_PTR PRP_REG(0x10) +#define PRP_SOURCE_CR_PTR PRP_REG(0x14) +#define PRP_DEST_RGB1_PTR PRP_REG(0x18) +#define PRP_DEST_RGB2_PTR PRP_REG(0x1C) +#define PRP_DEST_Y_PTR PRP_REG(0x20) +#define PRP_DEST_CB_PTR PRP_REG(0x24) +#define PRP_DEST_CR_PTR PRP_REG(0x28) +#define PRP_SOURCE_FRAME_SIZE PRP_REG(0x2C) +#define PRP_CH1_LINE_STRIDE PRP_REG(0x30) +#define PRP_SRC_PIXEL_FORMAT_CNTL PRP_REG(0x34) +#define PRP_CH1_PIXEL_FORMAT_CNTL PRP_REG(0x38) +#define PRP_CH1_OUT_IMAGE_SIZE PRP_REG(0x3C) +#define PRP_CH2_OUT_IMAGE_SIZE PRP_REG(0x40) +#define PRP_SOURCE_LINE_STRIDE PRP_REG(0x44) +#define PRP_CSC_COEF_012 PRP_REG(0x48) +#define PRP_CSC_COEF_345 PRP_REG(0x4C) +#define PRP_CSC_COEF_678 PRP_REG(0x50) +#define PRP_CH1_RZ_HORI_COEF1 PRP_REG(0x54) +#define PRP_CH1_RZ_HORI_COEF2 PRP_REG(0x58) +#define PRP_CH1_RZ_HORI_VALID PRP_REG(0x5C) +#define PRP_CH1_RZ_VERT_COEF1 PRP_REG(0x60) +#define PRP_CH1_RZ_VERT_COEF2 PRP_REG(0x64) +#define PRP_CH1_RZ_VERT_VALID PRP_REG(0x68) +#define PRP_CH2_RZ_HORI_COEF1 PRP_REG(0x6C) +#define PRP_CH2_RZ_HORI_COEF2 PRP_REG(0x70) +#define PRP_CH2_RZ_HORI_VALID PRP_REG(0x74) +#define PRP_CH2_RZ_VERT_COEF1 PRP_REG(0x78) +#define PRP_CH2_RZ_VERT_COEF2 PRP_REG(0x7C) +#define PRP_CH2_RZ_VERT_VALID PRP_REG(0x80) + +#define B_SET(b) (1 << (b)) + +/* Bit definitions for PrP control register */ +#define PRP_CNTL_RSTVAL 0x28 +#define PRP_CNTL_CH1EN B_SET(0) +#define PRP_CNTL_CH2EN B_SET(1) +#define PRP_CNTL_CSI B_SET(2) +#define PRP_CNTL_IN_32 B_SET(3) +#define PRP_CNTL_IN_RGB B_SET(4) +#define PRP_CNTL_IN_YUV420 0 +#define PRP_CNTL_IN_YUV422 PRP_CNTL_IN_32 +#define PRP_CNTL_IN_RGB16 PRP_CNTL_IN_RGB +#define PRP_CNTL_IN_RGB32 (PRP_CNTL_IN_RGB | PRP_CNTL_IN_32) +#define PRP_CNTL_CH1_RGB8 0 +#define PRP_CNTL_CH1_RGB16 B_SET(5) +#define PRP_CNTL_CH1_RGB32 B_SET(6) +#define PRP_CNTL_CH1_YUV422 (B_SET(5) | B_SET(6)) +#define PRP_CNTL_CH2_YUV420 0 +#define PRP_CNTL_CH2_YUV422 B_SET(7) +#define PRP_CNTL_CH2_YUV444 B_SET(8) +#define PRP_CNTL_CH1_LOOP B_SET(9) +#define PRP_CNTL_CH2_LOOP B_SET(10) +#define PRP_CNTL_AUTODROP B_SET(11) +#define PRP_CNTL_RST B_SET(12) +#define PRP_CNTL_CNTREN B_SET(13) +#define PRP_CNTL_WINEN B_SET(14) +#define PRP_CNTL_UNCHAIN B_SET(15) +#define PRP_CNTL_IN_SKIP_NONE 0 +#define PRP_CNTL_IN_SKIP_1_2 B_SET(16) +#define PRP_CNTL_IN_SKIP_1_3 B_SET(17) +#define PRP_CNTL_IN_SKIP_2_3 (B_SET(16) | B_SET(17)) +#define PRP_CNTL_IN_SKIP_1_4 B_SET(18) +#define PRP_CNTL_IN_SKIP_3_4 (B_SET(16) | B_SET(18)) +#define PRP_CNTL_IN_SKIP_2_5 (B_SET(17) | B_SET(18)) +#define PRP_CNTL_IN_SKIP_3_5 (B_SET(16) | B_SET(17) | B_SET(18)) +#define PRP_CNTL_CH1_SKIP_NONE 0 +#define PRP_CNTL_CH1_SKIP_1_2 B_SET(19) +#define PRP_CNTL_CH1_SKIP_1_3 B_SET(20) +#define PRP_CNTL_CH1_SKIP_2_3 (B_SET(19) | B_SET(20)) +#define PRP_CNTL_CH1_SKIP_1_4 B_SET(21) +#define PRP_CNTL_CH1_SKIP_3_4 (B_SET(19) | B_SET(21)) +#define PRP_CNTL_CH1_SKIP_2_5 (B_SET(20) | B_SET(21)) +#define PRP_CNTL_CH1_SKIP_3_5 (B_SET(19) | B_SET(20) | B_SET(21)) +#define PRP_CNTL_CH2_SKIP_NONE 0 +#define PRP_CNTL_CH2_SKIP_1_2 B_SET(22) +#define PRP_CNTL_CH2_SKIP_1_3 B_SET(23) +#define PRP_CNTL_CH2_SKIP_2_3 (B_SET(22) | B_SET(23)) +#define PRP_CNTL_CH2_SKIP_1_4 B_SET(24) +#define PRP_CNTL_CH2_SKIP_3_4 (B_SET(22) | B_SET(24)) +#define PRP_CNTL_CH2_SKIP_2_5 (B_SET(23) | B_SET(24)) +#define PRP_CNTL_CH2_SKIP_3_5 (B_SET(22) | B_SET(23) | B_SET(24)) +#define PRP_CNTL_FIFO_I128 0 +#define PRP_CNTL_FIFO_I96 B_SET(25) +#define PRP_CNTL_FIFO_I64 B_SET(26) +#define PRP_CNTL_FIFO_I32 (B_SET(25) | B_SET(26)) +#define PRP_CNTL_FIFO_O64 0 +#define PRP_CNTL_FIFO_O48 B_SET(27) +#define PRP_CNTL_FIFO_O32 B_SET(28) +#define PRP_CNTL_FIFO_O16 (B_SET(27) | B_SET(28)) +#define PRP_CNTL_CH2B1 B_SET(29) +#define PRP_CNTL_CH2B2 B_SET(30) +#define PRP_CNTL_CH2_FLOWEN B_SET(31) + +/* Bit definitions for PrP interrupt control register */ +#define PRP_INTRCNTL_RDERR B_SET(0) +#define PRP_INTRCNTL_CH1WERR B_SET(1) +#define PRP_INTRCNTL_CH2WERR B_SET(2) +#define PRP_INTRCNTL_CH1FC B_SET(3) +#define PRP_INTRCNTL_CH2FC B_SET(5) +#define PRP_INTRCNTL_LBOVF B_SET(7) +#define PRP_INTRCNTL_CH2OVF B_SET(8) + +/* Bit definitions for PrP interrupt status register */ +#define PRP_INTRSTAT_RDERR B_SET(0) +#define PRP_INTRSTAT_CH1WERR B_SET(1) +#define PRP_INTRSTAT_CH2WERR B_SET(2) +#define PRP_INTRSTAT_CH2BUF2 B_SET(3) +#define PRP_INTRSTAT_CH2BUF1 B_SET(4) +#define PRP_INTRSTAT_CH1BUF2 B_SET(5) +#define PRP_INTRSTAT_CH1BUF1 B_SET(6) +#define PRP_INTRSTAT_LBOVF B_SET(7) +#define PRP_INTRSTAT_CH2OVF B_SET(8) + +#define PRP_CHANNEL_1 0x1 +#define PRP_CHANNEL_2 0x2 + +/* PRP-CSI config */ +#define PRP_CSI_EN 0x80 +#define PRP_CSI_LOOP (0x40 | PRP_CSI_EN) +#define PRP_CSI_IRQ_FRM (0x08 | PRP_CSI_LOOP) +#define PRP_CSI_IRQ_CH1ERR (0x10 | PRP_CSI_LOOP) +#define PRP_CSI_IRQ_CH2ERR (0x20 | PRP_CSI_LOOP) +#define PRP_CSI_IRQ_ALL (0x38 | PRP_CSI_LOOP) +#define PRP_CSI_SKIP_NONE 0 +#define PRP_CSI_SKIP_1OF2 1 +#define PRP_CSI_SKIP_1OF3 2 +#define PRP_CSI_SKIP_2OF3 3 +#define PRP_CSI_SKIP_1OF4 4 +#define PRP_CSI_SKIP_3OF4 5 +#define PRP_CSI_SKIP_2OF5 6 +#define PRP_CSI_SKIP_4OF5 7 + +#define PRP_PIXIN_RGB565 0x2CA00565 +#define PRP_PIXIN_RGB888 0x41000888 +#define PRP_PIXIN_YUV420 0 +#define PRP_PIXIN_YUYV 0x22000888 +#define PRP_PIXIN_YVYU 0x20100888 +#define PRP_PIXIN_UYVY 0x03080888 +#define PRP_PIXIN_VYUY 0x01180888 +#define PRP_PIXIN_YUV422 0x62080888 + +#define PRP_PIX1_RGB332 0x14400322 +#define PRP_PIX1_RGB565 0x2CA00565 +#define PRP_PIX1_RGB888 0x41000888 +#define PRP_PIX1_YUYV 0x62000888 +#define PRP_PIX1_YVYU 0x60100888 +#define PRP_PIX1_UYVY 0x43080888 +#define PRP_PIX1_VYUY 0x41180888 +#define PRP_PIX1_UNUSED 0 + +#define PRP_PIX2_YUV420 0 +#define PRP_PIX2_YUV422 1 +#define PRP_PIX2_YUV444 4 +#define PRP_PIX2_UNUSED 8 + +#define PRP_ALGO_WIDTH_ANY 0 +#define PRP_ALGO_HEIGHT_ANY 0 +#define PRP_ALGO_WIDTH_BIL 1 +#define PRP_ALGO_WIDTH_AVG 2 +#define PRP_ALGO_HEIGHT_BIL 4 +#define PRP_ALGO_HEIGHT_AVG 8 +#define PRP_ALGO_BYPASS 0x10 + +typedef struct _emma_prp_ratio { + unsigned short num; + unsigned short den; +} emma_prp_ratio; + +/* + * The following definitions are for resizing. Definition values must not + * be changed otherwise decision logic will be wrong. + */ +#define SCALE_RETRY 16 /* retry times if ratio is not supported */ + +#define BC_COEF 3 +#define MAX_TBL 20 +#define SZ_COEF (1 << BC_COEF) + +#define ALGO_AUTO 0 +#define ALGO_BIL 1 +#define ALGO_AVG 2 + +typedef struct { + char tbl[20]; /* table entries */ + char len; /* table length used */ + char algo; /* ALGO_xxx */ + char ratio[20]; /* ratios used */ +} scale_t; + +/* + * structure for prp scaling. + * algorithm - bilinear or averaging for each axis + * PRP_ALGO_WIDTH_x | PRP_ALGO_HEIGHT_x | PRP_ALGO_BYPASS + * PRP_ALGO_BYPASS - Ch1 will not use Ch2 scaling with this flag + */ +typedef struct _emma_prp_scale { + unsigned char algo; + emma_prp_ratio width; + emma_prp_ratio height; +} emma_prp_scale; + +typedef struct emma_prp_cfg { + unsigned int in_pix; /* PRP_PIXIN_xxx */ + unsigned short in_width; /* image width, 32 - 2044 */ + unsigned short in_height; /* image height, 32 - 2044 */ + unsigned char in_csi; /* PRP_CSI_SKIP_x | PRP_CSI_LOOP */ + unsigned short in_line_stride; /* in_line_stride and in_line_skip */ + unsigned short in_line_skip; /* allow cropping from CSI */ + unsigned int in_ptr; /* bus address */ + /* + * in_csc[9] = 1 -> Y-16 + * if in_csc[1..9] == 0 + * in_csc[0] represents YUV range 0-3 = A0,A1,B0,B1; + * else + * in_csc[0..9] represents either format + */ + unsigned short in_csc[10]; + + unsigned char ch2_pix; /* PRP_PIX2_xxx */ + emma_prp_scale ch2_scale; /* resizing paramters */ + unsigned short ch2_width; /* 4-2044, 0 = scaled */ + unsigned short ch2_height; /* 4-2044, 0 = scaled */ + unsigned int ch2_ptr; /* bus addr */ + unsigned int ch2_ptr2; /* bus addr for 2nd buf (loop mode) */ + unsigned char ch2_csi; /* PRP_CSI_SKIP_x | PRP_CSI_LOOP */ + + unsigned int ch1_pix; /* PRP_PIX1_xxx */ + emma_prp_scale ch1_scale; /* resizing parameters */ + unsigned short ch1_width; /* 4-2044, 0 = scaled */ + unsigned short ch1_height; /* 4-2044, 0 = scaled */ + unsigned short ch1_stride; /* 4-4088, 0 = ch1_width */ + unsigned int ch1_ptr; /* bus addr */ + unsigned int ch1_ptr2; /* bus addr for 2nd buf (loop mode) */ + unsigned char ch1_csi; /* PRP_CSI_SKIP_x | PRP_CSI_LOOP */ + + /* + * channel resizing coefficients + * scale[0] for channel 1 width + * scale[1] for channel 1 height + * scale[2] for channel 2 width + * scale[3] for channel 2 height + */ + scale_t scale[4]; +} emma_prp_cfg; + +int prphw_reset(void); +int prphw_enable(int channel); +int prphw_disable(int channel); +int prphw_inptr(emma_prp_cfg *); +int prphw_ch1ptr(emma_prp_cfg *); +int prphw_ch1ptr2(emma_prp_cfg *); +int prphw_ch2ptr(emma_prp_cfg *); +int prphw_ch2ptr2(emma_prp_cfg *); +int prphw_cfg(emma_prp_cfg *); +int prphw_isr(void); +void prphw_init(void); +void prphw_exit(void); + +/* + * scale out coefficient table + * din in scale numerator + * dout in scale denominator + * inv in pre-scale dimension + * vout in/out post-scale output dimension + * pout out post-scale internal dimension [opt] + * retry in retry times (round the output length) when need + */ +int prp_scale(scale_t * pscale, int din, int dout, int inv, + unsigned short *vout, unsigned short *pout, int retry); + +int prp_init(void *dev_id); +void prp_exit(void *dev_id); +int prp_enc_select(void *data); +int prp_enc_deselect(void *data); +int prp_vf_select(void *data); +int prp_vf_deselect(void *data); +int prp_still_select(void *data); +int prp_still_deselect(void *data); + +#endif /* __MX27_PRP_H__ */ diff --git a/drivers/media/video/mxc/capture/mx27_prphw.c b/drivers/media/video/mxc/capture/mx27_prphw.c new file mode 100644 index 000000000000..c56a6df1716e --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_prphw.c @@ -0,0 +1,1099 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_prphw.c + * + * @brief MX27 Video For Linux 2 capture driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/clk.h> +#include <asm/io.h> +#include <linux/delay.h> + +#include "mx27_prp.h" + +#define PRP_MIN_IN_WIDTH 32 +#define PRP_MAX_IN_WIDTH 2044 +#define PRP_MIN_IN_HEIGHT 32 +#define PRP_MAX_IN_HEIGHT 2044 + +typedef struct _coeff_t { + unsigned long coeff[2]; + unsigned long cntl; +} coeff_t[2][2]; + +static coeff_t *PRP_RSZ_COEFF = (coeff_t *) PRP_CH1_RZ_HORI_COEF1; + +static unsigned char scale_get(scale_t * t, + unsigned char *i, unsigned char *out); +static int gcd(int x, int y); +static int ratio(int x, int y, int *den); +static int prp_scale_bilinear(scale_t * t, int coeff, int base, int nxt); +static int prp_scale_ave(scale_t * t, unsigned char base); +static int ave_scale(scale_t * t, int inv, int outv); +static int scale(scale_t * t, int inv, int outv); + +/*! + * @param t table + * @param i table index + * @param out bilinear # input pixels to advance + * average whether result is ready for output + * @return coefficient +*/ +static unsigned char scale_get(scale_t * t, unsigned char *i, + unsigned char *out) +{ + unsigned char c; + + c = t->tbl[*i]; + (*i)++; + *i %= t->len; + + if (out) { + if (t->algo == ALGO_BIL) { + for ((*out) = 1; + (*i) && ((*i) < t->len) && !t->tbl[(*i)]; (*i)++) { + (*out)++; + } + if ((*i) == t->len) + (*i) = 0; + } else + *out = c >> BC_COEF; + } + + c &= SZ_COEF - 1; + + if (c == SZ_COEF - 1) + c = SZ_COEF; + + return c; +} + +/*! + * @brief Get maximum common divisor. + * @param x First input value + * @param y Second input value + * @return Maximum common divisor of x and y + */ +static int gcd(int x, int y) +{ + int k; + + if (x < y) { + k = x; + x = y; + y = k; + } + + while ((k = x % y)) { + x = y; + y = k; + } + + return y; +} + +/*! + * @brief Get ratio. + * @param x First input value + * @param y Second input value + * @param den Denominator of the ratio (corresponding to y) + * @return Numerator of the ratio (corresponding to x) + */ +static int ratio(int x, int y, int *den) +{ + int g; + + if (!x || !y) + return 0; + + g = gcd(x, y); + *den = y / g; + + return x / g; +} + +/*! + * @brief Build PrP coefficient entry based on bilinear algorithm + * + * @param t The pointer to scale_t structure + * @param coeff The weighting coefficient + * @param base The base of the coefficient + * @param nxt Number of pixels to be read + * + * @return The length of current coefficient table on success + * -1 on failure + */ +static int prp_scale_bilinear(scale_t * t, int coeff, int base, int nxt) +{ + int i; + + if (t->len >= sizeof(t->tbl)) + return -1; + + coeff = ((coeff << BC_COEF) + (base >> 1)) / base; + if (coeff >= SZ_COEF - 1) + coeff--; + + coeff |= SZ_COEF; + t->tbl[(int)t->len++] = (unsigned char)coeff; + + for (i = 1; i < nxt; i++) { + if (t->len >= MAX_TBL) + return -1; + + t->tbl[(int)t->len++] = 0; + } + + return t->len; +} + +#define _bary(name) static const unsigned char name[] + +_bary(c1) = { +7}; + +_bary(c2) = { +4, 4}; + +_bary(c3) = { +2, 4, 2}; + +_bary(c4) = { +2, 2, 2, 2}; + +_bary(c5) = { +1, 2, 2, 2, 1}; + +_bary(c6) = { +1, 1, 2, 2, 1, 1}; + +_bary(c7) = { +1, 1, 1, 2, 1, 1, 1}; + +_bary(c8) = { +1, 1, 1, 1, 1, 1, 1, 1}; + +_bary(c9) = { +1, 1, 1, 1, 1, 1, 1, 1, 0}; + +_bary(c10) = { +0, 1, 1, 1, 1, 1, 1, 1, 1, 0}; + +_bary(c11) = { +0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}; + +_bary(c12) = { +0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0}; + +_bary(c13) = { +0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0}; + +_bary(c14) = { +0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0}; + +_bary(c15) = { +0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0}; + +_bary(c16) = { +1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}; + +_bary(c17) = { +0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}; + +_bary(c18) = { +0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0}; + +_bary(c19) = { +0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0}; + +_bary(c20) = { +0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0}; + +static const unsigned char *ave_coeff[] = { + c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, + c11, c12, c13, c14, c15, c16, c17, c18, c19, c20 +}; + +/*! + * @brief Build PrP coefficient table based on average algorithm + * + * @param t The pointer to scale_t structure + * @param base The base of the coefficient + * + * @return The length of current coefficient table on success + * -1 on failure + */ +static int prp_scale_ave(scale_t * t, unsigned char base) +{ + if (t->len + base > sizeof(t->tbl)) + return -1; + + memcpy(&t->tbl[(int)t->len], ave_coeff[(int)base - 1], base); + t->len = (unsigned char)(t->len + base); + t->tbl[t->len - 1] |= SZ_COEF; + + return t->len; +} + +/*! + * @brief Build PrP coefficient table based on average algorithm + * + * @param t The pointer to scale_t structure + * @param inv Input resolution + * @param outv Output resolution + * + * @return The length of current coefficient table on success + * -1 on failure + */ +static int ave_scale(scale_t * t, int inv, int outv) +{ + int ratio_count; + + ratio_count = 0; + if (outv != 1) { + unsigned char a[20]; + int v; + + /* split n:m into multiple n[i]:1 */ + for (v = 0; v < outv; v++) + a[v] = (unsigned char)(inv / outv); + + inv %= outv; + if (inv) { + /* find start of next layer */ + v = (outv - inv) >> 1; + inv += v; + for (; v < inv; v++) + a[v]++; + } + + for (v = 0; v < outv; v++) { + if (prp_scale_ave(t, a[v]) < 0) + return -1; + + t->ratio[ratio_count] = a[v]; + ratio_count++; + } + } else if (prp_scale_ave(t, inv) < 0) { + return -1; + } else { + t->ratio[ratio_count++] = (char)inv; + ratio_count++; + } + + return t->len; +} + +/*! + * @brief Build PrP coefficient table + * + * @param t The pointer to scale_t structure + * @param inv input resolution reduced ratio + * @param outv output resolution reduced ratio + * + * @return The length of current coefficient table on success + * -1 on failure + */ +static int scale(scale_t * t, int inv, int outv) +{ + int v; /* overflow counter */ + int coeff, nxt; /* table output */ + + t->len = 0; + if (t->algo == ALGO_AUTO) { + /* automatic choice - bilinear for shrinking less than 2:1 */ + t->algo = ((outv != inv) && ((2 * outv) > inv)) ? + ALGO_BIL : ALGO_AVG; + } + + /* 1:1 resize must use averaging, bilinear will hang */ + if ((inv == outv) && (t->algo == ALGO_BIL)) { + pr_debug("Warning: 1:1 resize must use averaging algo\n"); + t->algo = ALGO_AVG; + } + + memset(t->tbl, 0, sizeof(t->tbl)); + if (t->algo == ALGO_BIL) { + t->ratio[0] = (char)inv; + t->ratio[1] = (char)outv; + } else + memset(t->ratio, 0, sizeof(t->ratio)); + + if (inv == outv) { + /* force scaling */ + t->ratio[0] = 1; + if (t->algo == ALGO_BIL) + t->ratio[1] = 1; + + return prp_scale_ave(t, 1); + } + + if (inv < outv) { + pr_debug("Upscaling not supported %d:%d\n", inv, outv); + return -1; + } + + if (t->algo != ALGO_BIL) + return ave_scale(t, inv, outv); + + v = 0; + if (inv >= 2 * outv) { + /* downscale: >=2:1 bilinear approximation */ + coeff = inv - 2 * outv; + v = 0; + nxt = 0; + do { + v += coeff; + nxt = 2; + while (v >= outv) { + v -= outv; + nxt++; + } + + if (prp_scale_bilinear(t, 1, 2, nxt) < 0) + return -1; + } while (v); + } else { + /* downscale: bilinear */ + int in_pos_inc = 2 * outv; + int out_pos = inv; + int out_pos_inc = 2 * inv; + int init_carry = inv - outv; + int carry = init_carry; + + v = outv + in_pos_inc; + do { + coeff = v - out_pos; + out_pos += out_pos_inc; + carry += out_pos_inc; + for (nxt = 0; v < out_pos; nxt++) { + v += in_pos_inc; + carry -= in_pos_inc; + } + if (prp_scale_bilinear(t, coeff, in_pos_inc, nxt) < 0) + return -1; + } while (carry != init_carry); + } + return t->len; +} + +/*! + * @brief Build PrP coefficient table + * + * @param pscale The pointer to scale_t structure which holdes + * coefficient tables + * @param din Scale ratio numerator + * @param dout Scale ratio denominator + * @param inv Input resolution + * @param vout Output resolution + * @param pout Internal output resolution + * @param retry Retry times (round the output length) when need + * + * @return Zero on success, others on failure + */ +int prp_scale(scale_t * pscale, int din, int dout, int inv, + unsigned short *vout, unsigned short *pout, int retry) +{ + int num; + int den; + unsigned short outv; + + /* auto-generation of values */ + if (!(dout && din)) { + if (!*vout) + dout = din = 1; + else { + din = inv; + dout = *vout; + } + } + + if (din < dout) { + pr_debug("Scale err, unsupported ratio %d : %d\n", din, dout); + return -1; + } + + lp_retry: + num = ratio(din, dout, &den); + if (!num) { + pr_debug("Scale err, unsupported ratio %d : %d\n", din, dout); + return -1; + } + + if (num > MAX_TBL || scale(pscale, num, den) < 0) { + dout++; + if (retry--) + goto lp_retry; + + pr_debug("Scale err, unsupported ratio %d : %d\n", num, den); + return -1; + } + + if (pscale->algo == ALGO_BIL) { + unsigned char i, j, k; + + outv = + (unsigned short)(inv / pscale->ratio[0] * pscale->ratio[1]); + inv %= pscale->ratio[0]; + for (i = j = 0; inv > 0; j++) { + unsigned char nxt; + + k = scale_get(pscale, &i, &nxt); + if (inv == 1 && k < SZ_COEF) { + /* needs 2 pixels for this output */ + break; + } + inv -= nxt; + } + outv = outv + j; + } else { + unsigned char i, tot; + + for (tot = i = 0; pscale->ratio[i]; i++) + tot = tot + pscale->ratio[i]; + + outv = (unsigned short)(inv / tot) * i; + inv %= tot; + for (i = 0; inv > 0; i++, outv++) + inv -= pscale->ratio[i]; + } + + if (!(*vout) || ((*vout) > outv)) + *vout = outv; + + if (pout) + *pout = outv; + + return 0; +} + +/*! + * @brief Reset PrP block + */ +int prphw_reset(void) +{ + unsigned long val; + unsigned long flag; + int i; + + flag = PRP_CNTL_RST; + val = PRP_CNTL_RSTVAL; + + __raw_writel(flag, PRP_CNTL); + + /* timeout */ + for (i = 0; i < 1000; i++) { + if (!(__raw_readl(PRP_CNTL) & flag)) { + pr_debug("PrP reset over\n"); + break; + } + msleep(1); + } + + /* verify reset value */ + if (__raw_readl(PRP_CNTL) != val) { + pr_info("PrP reset err, val = 0x%08X\n", __raw_readl(PRP_CNTL)); + return -1; + } + + return 0; +} + +/*! + * @brief Enable PrP channel. + * @param channel Channel number to be enabled + * @return Zero on success, others on failure + */ +int prphw_enable(int channel) +{ + unsigned long val; + + val = __raw_readl(PRP_CNTL); + if (channel & PRP_CHANNEL_1) + val |= PRP_CNTL_CH1EN; + if (channel & PRP_CHANNEL_2) + val |= (PRP_CNTL_CH2EN | PRP_CNTL_CH2_FLOWEN); + + __raw_writel(val, PRP_CNTL); + + return 0; +} + +/*! + * @brief Disable PrP channel. + * @param channel Channel number to be disable + * @return Zero on success, others on failure + */ +int prphw_disable(int channel) +{ + unsigned long val; + + val = __raw_readl(PRP_CNTL); + if (channel & PRP_CHANNEL_1) + val &= ~PRP_CNTL_CH1EN; + if (channel & PRP_CHANNEL_2) + val &= ~(PRP_CNTL_CH2EN | PRP_CNTL_CH2_FLOWEN); + + __raw_writel(val, PRP_CNTL); + + return 0; +} + +/*! + * @brief Set PrP input buffer address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_inptr(emma_prp_cfg * cfg) +{ + if (cfg->in_csi & PRP_CSI_EN) + return -1; + + __raw_writel(cfg->in_ptr, PRP_SOURCE_Y_PTR); + if (cfg->in_pix == PRP_PIXIN_YUV420) { + u32 size; + + size = cfg->in_line_stride * cfg->in_height; + __raw_writel(cfg->in_ptr + size, PRP_SOURCE_CB_PTR); + __raw_writel(cfg->in_ptr + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } + return 0; +} + +/*! + * @brief Set PrP channel 1 output buffer 1 address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_ch1ptr(emma_prp_cfg * cfg) +{ + if (cfg->ch1_pix == PRP_PIX1_UNUSED) + return -1; + + __raw_writel(cfg->ch1_ptr, PRP_DEST_RGB1_PTR); + + /* support double buffer in loop mode only */ + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (cfg->ch1_ptr2) + __raw_writel(cfg->ch1_ptr2, PRP_DEST_RGB2_PTR); + else + __raw_writel(cfg->ch1_ptr, PRP_DEST_RGB2_PTR); + } + + return 0; +} + +/*! + * @brief Set PrP channel 1 output buffer 2 address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_ch1ptr2(emma_prp_cfg * cfg) +{ + if (cfg->ch1_pix == PRP_PIX1_UNUSED || + (cfg->in_csi & PRP_CSI_LOOP) != PRP_CSI_LOOP) + return -1; + + if (cfg->ch1_ptr2) + __raw_writel(cfg->ch1_ptr2, PRP_DEST_RGB2_PTR); + else + return -1; + + return 0; +} + +/*! + * @brief Set PrP channel 2 output buffer 1 address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_ch2ptr(emma_prp_cfg * cfg) +{ + u32 size; + + if (cfg->ch2_pix == PRP_PIX2_UNUSED) + return -1; + + __raw_writel(cfg->ch2_ptr, PRP_DEST_Y_PTR); + + if (cfg->ch2_pix == PRP_PIX2_YUV420) { + size = cfg->ch2_width * cfg->ch2_height; + __raw_writel(cfg->ch2_ptr + size, PRP_DEST_CB_PTR); + __raw_writel(cfg->ch2_ptr + size + (size >> 2), + PRP_DEST_CR_PTR); + } + + __raw_writel(__raw_readl(PRP_CNTL) | PRP_CNTL_CH2B1, PRP_CNTL); + return 0; +} + +/*! + * @brief Set PrP channel 2 output buffer 2 address. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_ch2ptr2(emma_prp_cfg * cfg) +{ + u32 size; + + if (cfg->ch2_pix == PRP_PIX2_UNUSED || + (cfg->in_csi & PRP_CSI_LOOP) != PRP_CSI_LOOP) + return -1; + + __raw_writel(cfg->ch2_ptr2, PRP_SOURCE_Y_PTR); + if (cfg->ch2_pix == PRP_PIX2_YUV420) { + size = cfg->ch2_width * cfg->ch2_height; + __raw_writel(cfg->ch2_ptr2 + size, PRP_SOURCE_CB_PTR); + __raw_writel(cfg->ch2_ptr2 + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } + + __raw_writel(__raw_readl(PRP_CNTL) | PRP_CNTL_CH2B2, PRP_CNTL); + return 0; +} + +/*! + * @brief Build CSC table + * @param csc CSC table + * in csc[0]=index 0..3 : A.1 A.0 B.1 B.0 + * csc[1]=direction 0 : YUV2RGB 1 : RGB2YUV + * out csc[0..4] are coefficients c[9] is offset + * csc[0..8] are coefficients c[9] is offset + */ +void csc_tbl(short csc[10]) +{ + static const unsigned short _r2y[][9] = { + {0x4D, 0x4B, 0x3A, 0x57, 0x55, 0x40, 0x40, 0x6B, 0x29}, + {0x42, 0x41, 0x32, 0x4C, 0x4A, 0x38, 0x38, 0x5E, 0x24}, + {0x36, 0x5C, 0x25, 0x3B, 0x63, 0x40, 0x40, 0x74, 0x18}, + {0x2F, 0x4F, 0x20, 0x34, 0x57, 0x38, 0x38, 0x66, 0x15}, + }; + static const unsigned short _y2r[][5] = { + {0x80, 0xb4, 0x2c, 0x5b, 0x0e4}, + {0x95, 0xcc, 0x32, 0x68, 0x104}, + {0x80, 0xca, 0x18, 0x3c, 0x0ec}, + {0x95, 0xe5, 0x1b, 0x44, 0x1e0}, + }; + unsigned short *_csc; + int _csclen; + + csc[9] = csc[0] & 1; + _csclen = csc[0] & 3; + + if (csc[1]) { + _csc = (unsigned short *)_r2y[_csclen]; + _csclen = sizeof(_r2y[0]); + } else { + _csc = (unsigned short *)_y2r[_csclen]; + _csclen = sizeof(_y2r[0]); + memset(csc + 5, 0, sizeof(short) * 4); + } + memcpy(csc, _csc, _csclen); +} + +/*! + * @brief Setup PrP resize coefficient registers + * + * @param ch PrP channel number + * @param dir Direction, 0 - horizontal, 1 - vertical + * @param scale The pointer to scale_t structure + */ +static void prp_set_scaler(int ch, int dir, scale_t * scale) +{ + int i; + unsigned int coeff[2]; + unsigned int valid; + + for (coeff[0] = coeff[1] = valid = 0, i = 19; i >= 0; i--) { + int j; + + j = i > 9 ? 1 : 0; + coeff[j] = (coeff[j] << BC_COEF) | + (scale->tbl[i] & (SZ_COEF - 1)); + + if (i == 5 || i == 15) + coeff[j] <<= 1; + + valid = (valid << 1) | (scale->tbl[i] >> BC_COEF); + } + + valid |= (scale->len << 24) | ((2 - scale->algo) << 31); + + for (i = 0; i < 2; i++) + (*PRP_RSZ_COEFF)[1 - ch][dir].coeff[i] = coeff[i]; + + (*PRP_RSZ_COEFF)[1 - ch][dir].cntl = valid; +} + +/*! + * @brief Setup PrP registers relevant to input. + * @param cfg Pointer to PrP configuration parameter + * @param prp_cntl Holds the value for PrP control register + * @return Zero on success, others on failure + */ +static int prphw_input_cfg(emma_prp_cfg * cfg, unsigned long *prp_cntl) +{ + unsigned long mask; + + switch (cfg->in_pix) { + case PRP_PIXIN_YUV420: + *prp_cntl |= PRP_CNTL_IN_YUV420; + mask = 0x7; + break; + case PRP_PIXIN_YUYV: + case PRP_PIXIN_YVYU: + case PRP_PIXIN_UYVY: + case PRP_PIXIN_VYUY: + *prp_cntl |= PRP_CNTL_IN_YUV422; + mask = 0x1; + break; + case PRP_PIXIN_RGB565: + *prp_cntl |= PRP_CNTL_IN_RGB16; + mask = 0x1; + break; + case PRP_PIXIN_RGB888: + *prp_cntl |= PRP_CNTL_IN_RGB32; + mask = 0; + break; + default: + pr_debug("Unsupported input pix format 0x%08X\n", cfg->in_pix); + return -1; + } + + /* align the input image width */ + if (cfg->in_width & mask) { + pr_debug("in_width misaligned. in_width=%d\n", cfg->in_width); + return -1; + } + + if ((cfg->in_width < PRP_MIN_IN_WIDTH) + || (cfg->in_width > PRP_MAX_IN_WIDTH)) { + pr_debug("Unsupported input width %d\n", cfg->in_width); + return -1; + } + + cfg->in_height &= ~1; /* truncate to make even */ + + if ((cfg->in_height < PRP_MIN_IN_HEIGHT) + || (cfg->in_height > PRP_MAX_IN_HEIGHT)) { + pr_debug("Unsupported input height %d\n", cfg->in_height); + return -1; + } + + if (!(cfg->in_csi & PRP_CSI_EN)) + if (!cfg->in_line_stride) + cfg->in_line_stride = cfg->in_width; + + __raw_writel(cfg->in_pix, PRP_SRC_PIXEL_FORMAT_CNTL); + __raw_writel((cfg->in_width << 16) | cfg->in_height, + PRP_SOURCE_FRAME_SIZE); + __raw_writel((cfg->in_line_skip << 16) | cfg->in_line_stride, + PRP_SOURCE_LINE_STRIDE); + + if (!(cfg->in_csi & PRP_CSI_EN)) { + __raw_writel(cfg->in_ptr, PRP_SOURCE_Y_PTR); + if (cfg->in_pix == PRP_PIXIN_YUV420) { + unsigned int size; + + size = cfg->in_line_stride * cfg->in_height; + __raw_writel(cfg->in_ptr + size, PRP_SOURCE_CB_PTR); + __raw_writel(cfg->in_ptr + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } + } + + /* always cropping */ + *prp_cntl |= PRP_CNTL_WINEN; + + /* color space conversion */ + if (!cfg->in_csc[1]) { + if (cfg->in_csc[0] > 3) { + pr_debug("in_csc invalid 0x%X\n", cfg->in_csc[0]); + return -1; + } + if ((cfg->in_pix == PRP_PIXIN_RGB565) + || (cfg->in_pix == PRP_PIXIN_RGB888)) + cfg->in_csc[1] = 1; + else + cfg->in_csc[0] = 0; + csc_tbl(cfg->in_csc); + } + + __raw_writel((cfg->in_csc[0] << 21) | (cfg->in_csc[1] << 11) + | cfg->in_csc[2], PRP_CSC_COEF_012); + __raw_writel((cfg->in_csc[3] << 21) | (cfg->in_csc[4] << 11) + | cfg->in_csc[5], PRP_CSC_COEF_345); + __raw_writel((cfg->in_csc[6] << 21) | (cfg->in_csc[7] << 11) + | cfg->in_csc[8] | (cfg->in_csc[9] << 31), + PRP_CSC_COEF_678); + + if (cfg->in_csi & PRP_CSI_EN) { + *prp_cntl |= PRP_CNTL_CSI; + + /* loop mode enable, ch1 ch2 together */ + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) + *prp_cntl |= (PRP_CNTL_CH1_LOOP | PRP_CNTL_CH2_LOOP); + } + + return 0; +} + +/*! + * @brief Setup PrP registers relevant to channel 2. + * @param cfg Pointer to PrP configuration parameter + * @param prp_cntl Holds the value for PrP control register + * @return Zero on success, others on failure + */ +static int prphw_ch2_cfg(emma_prp_cfg * cfg, unsigned long *prp_cntl) +{ + switch (cfg->ch2_pix) { + case PRP_PIX2_YUV420: + *prp_cntl |= PRP_CNTL_CH2_YUV420; + break; + case PRP_PIX2_YUV422: + *prp_cntl |= PRP_CNTL_CH2_YUV422; + break; + case PRP_PIX2_YUV444: + *prp_cntl |= PRP_CNTL_CH2_YUV444; + break; + case PRP_PIX2_UNUSED: + return 0; + default: + pr_debug("Unsupported channel 2 pix format 0x%08X\n", + cfg->ch2_pix); + return -1; + } + + if (cfg->ch2_pix == PRP_PIX2_YUV420) { + cfg->ch2_height &= ~1; /* ensure U/V presence */ + cfg->ch2_width &= ~7; /* ensure U/V word aligned */ + } else if (cfg->ch2_pix == PRP_PIX2_YUV422) { + cfg->ch2_width &= ~1; /* word aligned */ + } + + __raw_writel((cfg->ch2_width << 16) | cfg->ch2_height, + PRP_CH2_OUT_IMAGE_SIZE); + + if (cfg->ch2_pix == PRP_PIX2_YUV420) { + u32 size; + + /* Luminanance band start address */ + __raw_writel(cfg->ch2_ptr, PRP_DEST_Y_PTR); + + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (!cfg->ch2_ptr2) + __raw_writel(cfg->ch2_ptr, PRP_SOURCE_Y_PTR); + else + __raw_writel(cfg->ch2_ptr2, PRP_SOURCE_Y_PTR); + } + + /* Cb and Cr band start address */ + size = cfg->ch2_width * cfg->ch2_height; + __raw_writel(cfg->ch2_ptr + size, PRP_DEST_CB_PTR); + __raw_writel(cfg->ch2_ptr + size + (size >> 2), + PRP_DEST_CR_PTR); + + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (!cfg->ch2_ptr2) { + __raw_writel(cfg->ch2_ptr + size, + PRP_SOURCE_CB_PTR); + __raw_writel(cfg->ch2_ptr + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } else { + __raw_writel(cfg->ch2_ptr2 + size, + PRP_SOURCE_CB_PTR); + __raw_writel(cfg->ch2_ptr2 + size + (size >> 2), + PRP_SOURCE_CR_PTR); + } + } + } else { /* Pixel interleaved YUV422 or YUV444 */ + __raw_writel(cfg->ch2_ptr, PRP_DEST_Y_PTR); + + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (!cfg->ch2_ptr2) + __raw_writel(cfg->ch2_ptr, PRP_SOURCE_Y_PTR); + else + __raw_writel(cfg->ch2_ptr2, PRP_SOURCE_Y_PTR); + } + } + *prp_cntl |= PRP_CNTL_CH2B1 | PRP_CNTL_CH2B2; + + return 0; +} + +/*! + * @brief Setup PrP registers relevant to channel 1. + * @param cfg Pointer to PrP configuration parameter + * @param prp_cntl Holds the value for PrP control register + * @return Zero on success, others on failure + */ +static int prphw_ch1_cfg(emma_prp_cfg * cfg, unsigned long *prp_cntl) +{ + int ch1_bpp = 0; + + switch (cfg->ch1_pix) { + case PRP_PIX1_RGB332: + *prp_cntl |= PRP_CNTL_CH1_RGB8; + ch1_bpp = 1; + break; + case PRP_PIX1_RGB565: + *prp_cntl |= PRP_CNTL_CH1_RGB16; + ch1_bpp = 2; + break; + case PRP_PIX1_RGB888: + *prp_cntl |= PRP_CNTL_CH1_RGB32; + ch1_bpp = 4; + break; + case PRP_PIX1_YUYV: + case PRP_PIX1_YVYU: + case PRP_PIX1_UYVY: + case PRP_PIX1_VYUY: + *prp_cntl |= PRP_CNTL_CH1_YUV422; + ch1_bpp = 2; + break; + case PRP_PIX1_UNUSED: + return 0; + default: + pr_debug("Unsupported channel 1 pix format 0x%08X\n", + cfg->ch1_pix); + return -1; + } + + /* parallel or cascade resize */ + if (cfg->ch1_scale.algo & PRP_ALGO_BYPASS) + *prp_cntl |= PRP_CNTL_UNCHAIN; + + /* word align */ + if (ch1_bpp == 2) + cfg->ch1_width &= ~1; + else if (ch1_bpp == 1) + cfg->ch1_width &= ~3; + + if (!cfg->ch1_stride) + cfg->ch1_stride = cfg->ch1_width; + + __raw_writel(cfg->ch1_pix, PRP_CH1_PIXEL_FORMAT_CNTL); + __raw_writel((cfg->ch1_width << 16) | cfg->ch1_height, + PRP_CH1_OUT_IMAGE_SIZE); + __raw_writel(cfg->ch1_stride * ch1_bpp, PRP_CH1_LINE_STRIDE); + __raw_writel(cfg->ch1_ptr, PRP_DEST_RGB1_PTR); + + /* double buffer for loop mode */ + if ((cfg->in_csi & PRP_CSI_LOOP) == PRP_CSI_LOOP) { + if (cfg->ch1_ptr2) + __raw_writel(cfg->ch1_ptr2, PRP_DEST_RGB2_PTR); + else + __raw_writel(cfg->ch1_ptr, PRP_DEST_RGB2_PTR); + } + + return 0; +} + +/*! + * @brief Setup PrP registers. + * @param cfg Pointer to PrP configuration parameter + * @return Zero on success, others on failure + */ +int prphw_cfg(emma_prp_cfg * cfg) +{ + unsigned long prp_cntl = 0; + unsigned long val; + + /* input pixel format checking */ + if (prphw_input_cfg(cfg, &prp_cntl)) + return -1; + + if (prphw_ch2_cfg(cfg, &prp_cntl)) + return -1; + + if (prphw_ch1_cfg(cfg, &prp_cntl)) + return -1; + + /* register setting */ + __raw_writel(prp_cntl, PRP_CNTL); + + /* interrupt configuration */ + val = PRP_INTRCNTL_RDERR | PRP_INTRCNTL_LBOVF; + if (cfg->ch1_pix != PRP_PIX1_UNUSED) + val |= PRP_INTRCNTL_CH1FC | PRP_INTRCNTL_CH1WERR; + if (cfg->ch2_pix != PRP_PIX2_UNUSED) + val |= + PRP_INTRCNTL_CH2FC | PRP_INTRCNTL_CH2WERR | + PRP_INTRCNTL_CH2OVF; + __raw_writel(val, PRP_INTRCNTL); + + prp_set_scaler(1, 0, &cfg->scale[0]); /* Channel 1 width */ + prp_set_scaler(1, 1, &cfg->scale[1]); /* Channel 1 height */ + prp_set_scaler(0, 0, &cfg->scale[2]); /* Channel 2 width */ + prp_set_scaler(0, 1, &cfg->scale[3]); /* Channel 2 height */ + + return 0; +} + +/*! + * @brief Check PrP interrupt status. + * @return PrP interrupt status + */ +int prphw_isr(void) +{ + int status; + + status = __raw_readl(PRP_INTRSTATUS) & 0x1FF; + + if (status & (PRP_INTRSTAT_RDERR | PRP_INTRSTAT_CH1WERR | + PRP_INTRSTAT_CH2WERR)) + pr_debug("isr bus error. status= 0x%08X\n", status); + else if (status & PRP_INTRSTAT_CH2OVF) + pr_debug("isr ch 2 buffer overflow. status= 0x%08X\n", status); + else if (status & PRP_INTRSTAT_LBOVF) + pr_debug("isr line buffer overflow. status= 0x%08X\n", status); + + /* silicon bug?? enable bit does not self clear? */ + if (!(__raw_readl(PRP_CNTL) & PRP_CNTL_CH1_LOOP)) + __raw_writel(__raw_readl(PRP_CNTL) & (~PRP_CNTL_CH1EN), + PRP_CNTL); + if (!(__raw_readl(PRP_CNTL) & PRP_CNTL_CH2_LOOP)) + __raw_writel(__raw_readl(PRP_CNTL) & (~PRP_CNTL_CH2EN), + PRP_CNTL); + + __raw_writel(status, PRP_INTRSTATUS); /* clr irq */ + + return status; +} + +static struct clk *emma_clk; + +/*! + * @brief PrP module clock enable + */ +void prphw_init(void) +{ + emma_clk = clk_get(NULL, "emma_clk"); + clk_enable(emma_clk); +} + +/*! + * @brief PrP module clock disable + */ +void prphw_exit(void) +{ + clk_disable(emma_clk); + clk_put(emma_clk); +} diff --git a/drivers/media/video/mxc/capture/mx27_prpsw.c b/drivers/media/video/mxc/capture/mx27_prpsw.c new file mode 100644 index 000000000000..eca200a580f2 --- /dev/null +++ b/drivers/media/video/mxc/capture/mx27_prpsw.c @@ -0,0 +1,1042 @@ +/* + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_prpsw.c + * + * @brief MX27 Video For Linux 2 capture driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <asm/cacheflush.h> +#include <asm/io.h> +#include <asm/irq.h> + +#include "mxc_v4l2_capture.h" +#include "mx27_prp.h" +#include "mx27_csi.h" +#include "../drivers/video/mxc/mx2fb.h" +#include "../opl/opl.h" + +#define MEAN_COEF (SZ_COEF >> 1) + +static char prp_dev[] = "emma_prp"; +static int g_still_on = 0; +static emma_prp_cfg g_prp_cfg; +static int g_vfbuf, g_rotbuf; +static struct tasklet_struct prp_vf_tasklet; + +/* + * The following variables represents the virtual address for the cacheable + * buffers accessed by SW rotation/mirroring. The rotation/mirroring in + * cacheable buffers has significant performance improvement than it in + * non-cacheable buffers. + */ +static char *g_vaddr_vfbuf[2] = { 0, 0 }; +static char *g_vaddr_rotbuf[2] = { 0, 0 }; +static char *g_vaddr_fb = 0; + +static int set_ch1_addr(emma_prp_cfg * cfg, cam_data * cam); +static int prp_v4l2_cfg(emma_prp_cfg * cfg, cam_data * cam); +static int prp_vf_mem_alloc(cam_data * cam); +static void prp_vf_mem_free(cam_data * cam); +static int prp_rot_mem_alloc(cam_data * cam); +static void prp_rot_mem_free(cam_data * cam); +static int prp_enc_update_eba(u32 eba, int *buffer_num); +static int prp_enc_enable(void *private); +static int prp_enc_disable(void *private); +static int prp_vf_start(void *private); +static int prp_vf_stop(void *private); +static int prp_still_start(void *private); +static int prp_still_stop(void *private); +static irqreturn_t prp_isr(int irq, void *dev_id); +static void rotation(unsigned long private); +static int prp_resize_check_ch1(emma_prp_cfg * cfg); +static int prp_resize_check_ch2(emma_prp_cfg * cfg); + +#define PRP_DUMP(val) pr_debug("%s\t = 0x%08X\t%d\n", #val, val, val) + +/*! + * @brief Dump PrP configuration parameters. + * @param cfg The pointer to PrP configuration parameter + */ +static void prp_cfg_dump(emma_prp_cfg * cfg) +{ + PRP_DUMP(cfg->in_pix); + PRP_DUMP(cfg->in_width); + PRP_DUMP(cfg->in_height); + PRP_DUMP(cfg->in_csi); + PRP_DUMP(cfg->in_line_stride); + PRP_DUMP(cfg->in_line_skip); + PRP_DUMP(cfg->in_ptr); + + PRP_DUMP(cfg->ch1_pix); + PRP_DUMP(cfg->ch1_width); + PRP_DUMP(cfg->ch1_height); + PRP_DUMP(cfg->ch1_scale.algo); + PRP_DUMP(cfg->ch1_scale.width.num); + PRP_DUMP(cfg->ch1_scale.width.den); + PRP_DUMP(cfg->ch1_scale.height.num); + PRP_DUMP(cfg->ch1_scale.height.den); + PRP_DUMP(cfg->ch1_stride); + PRP_DUMP(cfg->ch1_ptr); + PRP_DUMP(cfg->ch1_ptr2); + PRP_DUMP(cfg->ch1_csi); + + PRP_DUMP(cfg->ch2_pix); + PRP_DUMP(cfg->ch2_width); + PRP_DUMP(cfg->ch2_height); + PRP_DUMP(cfg->ch2_scale.algo); + PRP_DUMP(cfg->ch2_scale.width.num); + PRP_DUMP(cfg->ch2_scale.width.den); + PRP_DUMP(cfg->ch2_scale.height.num); + PRP_DUMP(cfg->ch2_scale.height.den); + PRP_DUMP(cfg->ch2_ptr); + PRP_DUMP(cfg->ch2_ptr2); + PRP_DUMP(cfg->ch2_csi); +} + +/*! + * @brief Set PrP channel 1 output address. + * @param cfg Pointer to emma_prp_cfg structure + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int set_ch1_addr(emma_prp_cfg * cfg, cam_data * cam) +{ + if (cam->rotation != V4L2_MXC_ROTATE_NONE) { + cfg->ch1_ptr = (unsigned int)cam->rot_vf_bufs[0]; + cfg->ch1_ptr2 = (unsigned int)cam->rot_vf_bufs[1]; + if ((cam->rotation == V4L2_MXC_ROTATE_90_RIGHT) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_VFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_HFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_LEFT)) + cfg->ch1_stride = cam->win.w.height; + else + cfg->ch1_stride = cam->win.w.width; + + if (cam->v4l2_fb.flags != V4L2_FBUF_FLAG_OVERLAY) { + struct fb_info *fb = cam->overlay_fb; + if (!fb) + return -1; + if (g_vaddr_fb) + iounmap(g_vaddr_fb); + g_vaddr_fb = ioremap_cached(fb->fix.smem_start, + fb->fix.smem_len); + if (!g_vaddr_fb) + return -1; + } + } else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + cfg->ch1_ptr = (unsigned int)cam->vf_bufs[0]; + cfg->ch1_ptr2 = (unsigned int)cam->vf_bufs[1]; + cfg->ch1_stride = cam->win.w.width; + } else { + struct fb_info *fb = cam->overlay_fb; + + if (!fb) + return -1; + + cfg->ch1_ptr = fb->fix.smem_start; + cfg->ch1_ptr += cam->win.w.top * fb->var.xres_virtual + * (fb->var.bits_per_pixel >> 3) + + cam->win.w.left * (fb->var.bits_per_pixel >> 3); + cfg->ch1_ptr2 = cfg->ch1_ptr; + cfg->ch1_stride = fb->var.xres_virtual; + } + + return 0; +} + +/*! + * @brief Setup PrP configuration parameters. + * @param cfg Pointer to emma_prp_cfg structure + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_v4l2_cfg(emma_prp_cfg * cfg, cam_data * cam) +{ + cfg->in_pix = PRP_PIXIN_YUYV; + cfg->in_width = cam->crop_current.width; + cfg->in_height = cam->crop_current.height; + cfg->in_line_stride = cam->crop_current.left; + cfg->in_line_skip = cam->crop_current.top; + cfg->in_ptr = 0; + cfg->in_csi = PRP_CSI_LOOP; + memset(cfg->in_csc, 0, sizeof(cfg->in_csc)); + + if (cam->overlay_on) { + /* Convert V4L2 pixel format to PrP pixel format */ + switch (cam->v4l2_fb.fmt.pixelformat) { + case V4L2_PIX_FMT_RGB332: + cfg->ch1_pix = PRP_PIX1_RGB332; + break; + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_BGR32: + cfg->ch1_pix = PRP_PIX1_RGB888; + break; + case V4L2_PIX_FMT_YUYV: + cfg->ch1_pix = PRP_PIX1_YUYV; + break; + case V4L2_PIX_FMT_UYVY: + cfg->ch1_pix = PRP_PIX1_UYVY; + break; + case V4L2_PIX_FMT_RGB565: + default: + cfg->ch1_pix = PRP_PIX1_RGB565; + break; + } + if ((cam->rotation == V4L2_MXC_ROTATE_90_RIGHT) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_VFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_HFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_LEFT)) { + cfg->ch1_width = cam->win.w.height; + cfg->ch1_height = cam->win.w.width; + } else { + cfg->ch1_width = cam->win.w.width; + cfg->ch1_height = cam->win.w.height; + } + + if (set_ch1_addr(cfg, cam)) + return -1; + } else { + cfg->ch1_pix = PRP_PIX1_UNUSED; + cfg->ch1_width = cfg->in_width; + cfg->ch1_height = cfg->in_height; + } + cfg->ch1_scale.algo = 0; + cfg->ch1_scale.width.num = cfg->in_width; + cfg->ch1_scale.width.den = cfg->ch1_width; + cfg->ch1_scale.height.num = cfg->in_height; + cfg->ch1_scale.height.den = cfg->ch1_height; + cfg->ch1_csi = PRP_CSI_EN; + + if (cam->capture_on || g_still_on) { + switch (cam->v2f.fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUYV: + cfg->ch2_pix = PRP_PIX2_YUV422; + break; + case V4L2_PIX_FMT_YUV420: + cfg->ch2_pix = PRP_PIX2_YUV420; + break; + /* + * YUV444 is not defined by V4L2. + * We support it in default case. + */ + default: + cfg->ch2_pix = PRP_PIX2_YUV444; + break; + } + cfg->ch2_width = cam->v2f.fmt.pix.width; + cfg->ch2_height = cam->v2f.fmt.pix.height; + } else { + cfg->ch2_pix = PRP_PIX2_UNUSED; + cfg->ch2_width = cfg->in_width; + cfg->ch2_height = cfg->in_height; + } + cfg->ch2_scale.algo = 0; + cfg->ch2_scale.width.num = cfg->in_width; + cfg->ch2_scale.width.den = cfg->ch2_width; + cfg->ch2_scale.height.num = cfg->in_height; + cfg->ch2_scale.height.den = cfg->ch2_height; + cfg->ch2_csi = PRP_CSI_EN; + + memset(cfg->scale, 0, sizeof(cfg->scale)); + cfg->scale[0].algo = cfg->ch1_scale.algo & 3; + cfg->scale[1].algo = (cfg->ch1_scale.algo >> 2) & 3; + cfg->scale[2].algo = cfg->ch2_scale.algo & 3; + cfg->scale[3].algo = (cfg->ch2_scale.algo >> 2) & 3; + + prp_cfg_dump(cfg); + + if (prp_resize_check_ch2(cfg)) + return -1; + + if (prp_resize_check_ch1(cfg)) + return -1; + + return 0; +} + +/*! + * @brief PrP interrupt handler + */ +static irqreturn_t prp_isr(int irq, void *dev_id) +{ + int status; + cam_data *cam = (cam_data *) dev_id; + + status = prphw_isr(); + + if (g_still_on && (status & PRP_INTRSTAT_CH2BUF1)) { + prp_still_stop(cam); + cam->still_counter++; + wake_up_interruptible(&cam->still_queue); + /* + * Still & video capture use the same PrP channel 2. + * They are execlusive. + */ + } else if (cam->capture_on) { + if (status & (PRP_INTRSTAT_CH2BUF1 | PRP_INTRSTAT_CH2BUF2)) { + cam->enc_callback(0, cam); + } + } + if (cam->overlay_on + && (status & (PRP_INTRSTAT_CH1BUF1 | PRP_INTRSTAT_CH1BUF2))) { + if (cam->rotation != V4L2_MXC_ROTATE_NONE) { + g_rotbuf = (status & PRP_INTRSTAT_CH1BUF1) ? 0 : 1; + tasklet_schedule(&prp_vf_tasklet); + } else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + gwinfo.enabled = 1; + gwinfo.alpha_value = 255; + gwinfo.ck_enabled = 0; + gwinfo.xpos = cam->win.w.left; + gwinfo.ypos = cam->win.w.top; + gwinfo.xres = cam->win.w.width; + gwinfo.yres = cam->win.w.height; + gwinfo.xres_virtual = cam->win.w.width; + gwinfo.vs_reversed = 0; + if (status & PRP_INTRSTAT_CH1BUF1) + gwinfo.base = (unsigned long)cam->vf_bufs[0]; + else + gwinfo.base = (unsigned long)cam->vf_bufs[1]; + + mx2_gw_set(&gwinfo); + } + } + + return IRQ_HANDLED; +} + +/*! + * @brief PrP initialization. + * @param dev_id Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_init(void *dev_id) +{ + enable_irq(MXC_INT_EMMAPRP); + if (request_irq(MXC_INT_EMMAPRP, prp_isr, 0, prp_dev, dev_id)) + return -1; + prphw_init(); + + return 0; +} + +/*! + * @brief PrP initialization. + * @param dev_id Pointer to cam_data structure + */ +void prp_exit(void *dev_id) +{ + prphw_exit(); + disable_irq(MXC_INT_EMMAPRP); + free_irq(MXC_INT_EMMAPRP, dev_id); +} + +/*! + * @brief Update PrP channel 2 output buffer address. + * @param eba Physical address for PrP output buffer + * @param buffer_num The PrP channel 2 buffer number to be updated + * @return Zero on success, others on failure + */ +static int prp_enc_update_eba(u32 eba, int *buffer_num) +{ + if (*buffer_num) { + g_prp_cfg.ch2_ptr2 = eba; + prphw_ch2ptr2(&g_prp_cfg); + *buffer_num = 0; + } else { + g_prp_cfg.ch2_ptr = eba; + prphw_ch2ptr(&g_prp_cfg); + *buffer_num = 1; + } + + return 0; +} + +/*! + * @brief Enable PrP for encoding. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_enc_enable(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (prp_v4l2_cfg(&g_prp_cfg, cam)) + return -1; + + csi_enable_mclk(CSI_MCLK_ENC, true, true); + prphw_reset(); + + if (prphw_cfg(&g_prp_cfg)) + return -1; + + prphw_enable(cam->overlay_on ? (PRP_CHANNEL_1 | PRP_CHANNEL_2) + : PRP_CHANNEL_2); + + return 0; +} + +/*! + * @brief Disable PrP for encoding. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_enc_disable(void *private) +{ + prphw_disable(PRP_CHANNEL_2); + csi_enable_mclk(CSI_MCLK_ENC, false, false); + + return 0; +} + +/*! + * @brief Setup encoding functions. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_enc_select(void *private) +{ + int ret = 0; + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->enc_update_eba = prp_enc_update_eba; + cam->enc_enable = prp_enc_enable; + cam->enc_disable = prp_enc_disable; + } else + ret = -EIO; + + return ret; +} + +/*! + * @brief Uninstall encoding functions. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_enc_deselect(void *private) +{ + int ret = 0; + cam_data *cam = (cam_data *) private; + + ret = prp_enc_disable(private); + + if (cam) { + cam->enc_update_eba = NULL; + cam->enc_enable = NULL; + cam->enc_disable = NULL; + } + + return ret; +} + +/*! + * @brief Allocate memory for overlay. + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_vf_mem_alloc(cam_data * cam) +{ + int i; + + for (i = 0; i < 2; i++) { + cam->vf_bufs_size[i] = cam->win.w.width * cam->win.w.height * 2; + cam->vf_bufs_vaddr[i] = dma_alloc_coherent(0, + cam->vf_bufs_size[i], + &cam->vf_bufs[i], + GFP_DMA | + GFP_KERNEL); + if (!cam->vf_bufs_vaddr[i]) { + pr_debug("Failed to alloc memory for vf.\n"); + prp_vf_mem_free(cam); + return -1; + } + + g_vaddr_vfbuf[i] = + ioremap_cached(cam->vf_bufs[i], cam->vf_bufs_size[i]); + if (!g_vaddr_vfbuf[i]) { + pr_debug("Failed to ioremap_cached() for vf.\n"); + prp_vf_mem_free(cam); + return -1; + } + } + + return 0; +} + +/*! + * @brief Free memory for overlay. + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static void prp_vf_mem_free(cam_data * cam) +{ + int i; + + for (i = 0; i < 2; i++) { + if (cam->vf_bufs_vaddr[i]) { + dma_free_coherent(0, + cam->vf_bufs_size[i], + cam->vf_bufs_vaddr[i], + cam->vf_bufs[i]); + } + cam->vf_bufs[i] = 0; + cam->vf_bufs_vaddr[i] = 0; + cam->vf_bufs_size[i] = 0; + if (g_vaddr_vfbuf[i]) { + iounmap(g_vaddr_vfbuf[i]); + g_vaddr_vfbuf[i] = 0; + } + } +} + +/*! + * @brief Allocate intermediate memory for overlay rotation/mirroring. + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_rot_mem_alloc(cam_data * cam) +{ + int i; + + for (i = 0; i < 2; i++) { + cam->rot_vf_buf_size[i] = + cam->win.w.width * cam->win.w.height * 2; + cam->rot_vf_bufs_vaddr[i] = + dma_alloc_coherent(0, cam->rot_vf_buf_size[i], + &cam->rot_vf_bufs[i], + GFP_DMA | GFP_KERNEL); + if (!cam->rot_vf_bufs_vaddr[i]) { + pr_debug("Failed to alloc memory for vf rotation.\n"); + prp_rot_mem_free(cam); + return -1; + } + + g_vaddr_rotbuf[i] = + ioremap_cached(cam->rot_vf_bufs[i], + cam->rot_vf_buf_size[i]); + if (!g_vaddr_rotbuf[i]) { + pr_debug + ("Failed to ioremap_cached() for rotation buffer.\n"); + prp_rot_mem_free(cam); + return -1; + } + } + + return 0; +} + +/*! + * @brief Free intermedaite memory for overlay rotation/mirroring. + * @param cam Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static void prp_rot_mem_free(cam_data * cam) +{ + int i; + + for (i = 0; i < 2; i++) { + if (cam->rot_vf_bufs_vaddr[i]) { + dma_free_coherent(0, + cam->rot_vf_buf_size[i], + cam->rot_vf_bufs_vaddr[i], + cam->rot_vf_bufs[i]); + } + cam->rot_vf_bufs[i] = 0; + cam->rot_vf_bufs_vaddr[i] = 0; + cam->rot_vf_buf_size[i] = 0; + if (g_vaddr_rotbuf[i]) { + iounmap(g_vaddr_rotbuf[i]); + g_vaddr_rotbuf[i] = 0; + } + } +} + +/*! + * @brief Start overlay (view finder). + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_vf_start(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + prp_vf_mem_free(cam); + if (prp_vf_mem_alloc(cam)) { + pr_info("Error to allocate vf buffer\n"); + return -ENOMEM; + } + } + + if (cam->rotation != V4L2_MXC_ROTATE_NONE) { + prp_rot_mem_free(cam); + if (prp_rot_mem_alloc(cam)) { + pr_info("Error to allocate rotation buffer\n"); + prp_vf_mem_free(cam); + return -ENOMEM; + } + } + + if (prp_v4l2_cfg(&g_prp_cfg, cam)) { + prp_vf_mem_free(cam); + prp_rot_mem_free(cam); + return -1; + } + + csi_enable_mclk(CSI_MCLK_VF, true, true); + prphw_reset(); + + if (prphw_cfg(&g_prp_cfg)) { + prp_vf_mem_free(cam); + prp_rot_mem_free(cam); + return -1; + } + g_vfbuf = g_rotbuf = 0; + tasklet_init(&prp_vf_tasklet, rotation, (unsigned long)private); + + prphw_enable(cam->capture_on ? (PRP_CHANNEL_1 | PRP_CHANNEL_2) + : PRP_CHANNEL_1); + + return 0; +} + +/*! + * @brief Stop overlay (view finder). + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_vf_stop(void *private) +{ + cam_data *cam = (cam_data *) private; + + prphw_disable(PRP_CHANNEL_1); + + csi_enable_mclk(CSI_MCLK_VF, false, false); + tasklet_kill(&prp_vf_tasklet); + + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + /* Disable graphic window */ + gwinfo.enabled = 0; + mx2_gw_set(&gwinfo); + + prp_vf_mem_free(cam); + } + prp_rot_mem_free(cam); + if (g_vaddr_fb) { + iounmap(g_vaddr_fb); + g_vaddr_fb = 0; + } + + return 0; +} + +/*! + * @brief Setup overlay functions. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_vf_select(void *private) +{ + int ret = 0; + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->vf_start_sdc = prp_vf_start; + cam->vf_stop_sdc = prp_vf_stop; + cam->overlay_active = false; + } else + ret = -EIO; + + return ret; +} + +/*! + * @brief Uninstall overlay functions. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_vf_deselect(void *private) +{ + int ret = 0; + cam_data *cam = (cam_data *) private; + + ret = prp_vf_stop(private); + + if (cam) { + cam->vf_start_sdc = NULL; + cam->vf_stop_sdc = NULL; + } + + return ret; +} + +/*! + * @brief Start still picture capture. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_still_start(void *private) +{ + cam_data *cam = (cam_data *) private; + + g_still_on = 1; + g_prp_cfg.ch2_ptr = (unsigned int)cam->still_buf[0]; + g_prp_cfg.ch2_ptr2 = 0; + + if (prp_v4l2_cfg(&g_prp_cfg, cam)) + return -1; + + csi_enable_mclk(CSI_MCLK_RAW, true, true); + prphw_reset(); + + if (prphw_cfg(&g_prp_cfg)) { + g_still_on = 0; + return -1; + } + + prphw_enable(cam->overlay_on ? (PRP_CHANNEL_1 | PRP_CHANNEL_2) + : PRP_CHANNEL_2); + + return 0; +} + +/*! + * @brief Stop still picture capture. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +static int prp_still_stop(void *private) +{ + prphw_disable(PRP_CHANNEL_2); + + csi_enable_mclk(CSI_MCLK_RAW, false, false); + + g_still_on = 0; + + return 0; +} + +/*! + * @brief Setup functions for still picture capture. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_still_select(void *private) +{ + cam_data *cam = (cam_data *) private; + + if (cam) { + cam->csi_start = prp_still_start; + cam->csi_stop = prp_still_stop; + } + + return 0; +} + +/*! + * @brief Uninstall functions for still picture capture. + * @param private Pointer to cam_data structure + * @return Zero on success, others on failure + */ +int prp_still_deselect(void *private) +{ + cam_data *cam = (cam_data *) private; + int err = 0; + + err = prp_still_stop(cam); + + if (cam) { + cam->csi_start = NULL; + cam->csi_stop = NULL; + } + + return err; +} + +/*! + * @brief Perform software rotation or mirroring + * @param private Argument passed to the tasklet + */ +static void rotation(unsigned long private) +{ + char *src, *dst; + int width, height, s_stride, d_stride; + int size; + cam_data *cam = (cam_data *) private; + + src = g_vaddr_rotbuf[g_rotbuf]; + size = cam->rot_vf_buf_size[g_rotbuf]; + + if ((cam->rotation == V4L2_MXC_ROTATE_90_RIGHT) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_VFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_RIGHT_HFLIP) + || (cam->rotation == V4L2_MXC_ROTATE_90_LEFT)) { + width = cam->win.w.height; + height = cam->win.w.width; + s_stride = cam->win.w.height << 1; + } else { + width = cam->win.w.width; + height = cam->win.w.height; + s_stride = cam->win.w.width << 1; + } + + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + dst = g_vaddr_vfbuf[g_vfbuf]; + d_stride = cam->win.w.width << 1; + } else { /* The destination is the framebuffer */ + struct fb_info *fb = cam->overlay_fb; + if (!fb) + return; + dst = g_vaddr_fb; + dst += cam->win.w.top * fb->var.xres_virtual + * (fb->var.bits_per_pixel >> 3) + + cam->win.w.left * (fb->var.bits_per_pixel >> 3); + d_stride = fb->var.xres_virtual << 1; + } + + /* + * Invalidate the data in cache before performing the SW rotaion + * or mirroring in case the image size is less than QVGA. For image + * larger than QVGA it is not invalidated becase the invalidation + * will consume much time while we don't see any artifacts on the + * output if we don't perform invalidation for them. + * Similarly we don't flush the data after SW rotation/mirroring. + */ + if (size < 320 * 240 * 2) + dmac_inv_range(src, src + size); + switch (cam->rotation) { + case V4L2_MXC_ROTATE_VERT_FLIP: + opl_vmirror_u16(src, s_stride, width, height, dst, d_stride); + break; + case V4L2_MXC_ROTATE_HORIZ_FLIP: + opl_hmirror_u16(src, s_stride, width, height, dst, d_stride); + break; + case V4L2_MXC_ROTATE_180: + opl_rotate180_u16(src, s_stride, width, height, dst, d_stride); + break; + case V4L2_MXC_ROTATE_90_RIGHT: + opl_rotate90_u16(src, s_stride, width, height, dst, d_stride); + break; + case V4L2_MXC_ROTATE_90_RIGHT_VFLIP: + opl_rotate90_vmirror_u16(src, s_stride, width, height, dst, + d_stride); + break; + case V4L2_MXC_ROTATE_90_RIGHT_HFLIP: + /* ROTATE_90_RIGHT_HFLIP = ROTATE_270_RIGHT_VFLIP */ + opl_rotate270_vmirror_u16(src, s_stride, width, height, dst, + d_stride); + break; + case V4L2_MXC_ROTATE_90_LEFT: + opl_rotate270_u16(src, s_stride, width, height, dst, d_stride); + break; + default: + return; + } + + /* Config and display the graphic window */ + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + gwinfo.enabled = 1; + gwinfo.alpha_value = 255; + gwinfo.ck_enabled = 0; + gwinfo.xpos = cam->win.w.left; + gwinfo.ypos = cam->win.w.top; + gwinfo.xres = cam->win.w.width; + gwinfo.yres = cam->win.w.height; + gwinfo.xres_virtual = cam->win.w.width; + gwinfo.vs_reversed = 0; + gwinfo.base = (unsigned long)cam->vf_bufs[g_vfbuf]; + mx2_gw_set(&gwinfo); + + g_vfbuf = g_vfbuf ? 0 : 1; + } +} + +/* + * @brief Check if the resize ratio is supported based on the input and output + * dimension + * @param input input dimension + * @param output output dimension + * @return output dimension (should equal the parameter *output*) + * -1 on failure + */ +static int check_simple(scale_t * scale, int input, int output) +{ + unsigned short int_out; /* PrP internel width or height */ + unsigned short orig_out = output; + + if (prp_scale(scale, input, output, input, &orig_out, &int_out, 0)) + return -1; /* resize failed */ + else + return int_out; +} + +/* + * @brief Check if the resize ratio is supported based on the input and output + * dimension + * @param input input dimension + * @param output output dimension + * @return output dimension, may be rounded. + * -1 on failure + */ +static int check_simple_retry(scale_t * scale, int input, int output) +{ + unsigned short int_out; /* PrP internel width or height */ + unsigned short orig_out = output; + + if (prp_scale(scale, input, output, input, &orig_out, &int_out, + SCALE_RETRY)) + return -1; /* resize failed */ + else + return int_out; +} + +/*! + * @brief Check if the resize ratio is supported by PrP channel 1 + * @param cfg Pointer to emma_prp_cfg structure + * @return Zero on success, others on failure + */ +static int prp_resize_check_ch1(emma_prp_cfg * cfg) +{ + int in_w, in_h, ch1_w, ch1_h, ch2_w, ch2_h, w, h; + scale_t *pscale = &cfg->scale[0]; /* Ch1 width resize coeff */ + + if (cfg->ch1_pix == PRP_PIX1_UNUSED) + return 0; + + in_w = cfg->in_width; + in_h = cfg->in_height; + ch1_w = cfg->ch1_width; + ch1_h = cfg->ch1_height; + ch2_w = cfg->ch2_width; + ch2_h = cfg->ch2_height; + + /* + * For channel 1, try parallel resize first. If the resize + * ratio is not exactly supported, try cascade resize. If it + * still fails, use parallel resize but with rounded value. + */ + w = check_simple(pscale, in_w, ch1_w); + h = check_simple(pscale + 1, in_h, ch1_h); + if ((w == ch1_w) && (h == ch1_h)) + goto exit_parallel; + + if (cfg->ch2_pix != PRP_PIX2_UNUSED) { + /* + * Channel 2 is already used. The pscale is still pointing + * to ch1 resize coeff for temporary use. + */ + w = check_simple(pscale, in_w, ch2_w); + h = check_simple(pscale + 1, in_h, ch2_h); + if ((w == ch2_w) && (h == ch2_h)) { + /* Try cascade resize now */ + w = check_simple(pscale, ch2_w, ch1_w); + h = check_simple(pscale + 1, ch2_h, ch1_h); + if ((w == ch1_w) && (h == ch1_h)) + goto exit_cascade; + } + } else { + /* + * Try cascade resize for width, width is multiple of 2. + * Channel 2 is not used. So we have more values to pick + * for channel 2 resize. + */ + for (w = in_w - 2; w > ch1_w; w -= 2) { + /* Ch2 width resize */ + if (check_simple(pscale + 2, in_w, w) != w) + continue; + /* Ch1 width resize */ + if (check_simple(pscale, w, ch1_w) != ch1_w) + continue; + break; + } + if ((ch2_w = w) > ch1_w) { + /* try cascade resize for height */ + for (h = in_h - 1; h > ch1_h; h--) { + /* Ch2 height resize */ + if (check_simple(pscale + 3, in_h, h) != h) + continue; + /* Ch1 height resize */ + if (check_simple(pscale + 1, h, ch1_h) != ch1_h) + continue; + break; + } + if ((ch2_h = h) > ch1_h) + goto exit_cascade; + } + } + + /* Have to try parallel resize again and round the dimensions */ + w = check_simple_retry(pscale, in_w, ch1_w); + h = check_simple_retry(pscale + 1, in_h, ch1_h); + if ((w != -1) && (h != -1)) + goto exit_parallel; + + pr_debug("Ch1 resize error.\n"); + return -1; + + exit_parallel: + cfg->ch1_scale.algo |= PRP_ALGO_BYPASS; + pr_debug("ch1 parallel resize.\n"); + pr_debug("original width = %d internel width = %d\n", ch1_w, w); + pr_debug("original height = %d internel height = %d\n", ch1_h, h); + return 0; + + exit_cascade: + cfg->ch1_scale.algo &= ~PRP_ALGO_BYPASS; + pr_debug("ch1 cascade resize.\n"); + pr_debug("[width] in : ch2 : ch1=%d : %d : %d\n", in_w, ch2_w, ch1_w); + pr_debug("[height] in : ch2 : ch1=%d : %d : %d\n", in_h, ch2_h, ch1_h); + return 0; +} + +/*! + * @brief Check if the resize ratio is supported by PrP channel 2 + * @param cfg Pointer to emma_prp_cfg structure + * @return Zero on success, others on failure + */ +static int prp_resize_check_ch2(emma_prp_cfg * cfg) +{ + int w, h; + scale_t *pscale = &cfg->scale[2]; /* Ch2 width resize coeff */ + + if (cfg->ch2_pix == PRP_PIX2_UNUSED) + return 0; + + w = check_simple_retry(pscale, cfg->in_width, cfg->ch2_width); + h = check_simple_retry(pscale + 1, cfg->in_height, cfg->ch2_height); + if ((w != -1) && (h != -1)) { + pr_debug("Ch2 resize.\n"); + pr_debug("Original width = %d internel width = %d\n", + cfg->ch2_width, w); + pr_debug("Original height = %d internel height = %d\n", + cfg->ch2_height, h); + return 0; + } else { + pr_debug("Ch2 resize error.\n"); + return -1; + } +} diff --git a/drivers/media/video/mxc/capture/mxc_v4l2_capture.c b/drivers/media/video/mxc/capture/mxc_v4l2_capture.c new file mode 100644 index 000000000000..f08cfe8dfcc1 --- /dev/null +++ b/drivers/media/video/mxc/capture/mxc_v4l2_capture.c @@ -0,0 +1,2728 @@ +/* + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/capture/mxc_v4l2_capture.c + * + * @brief Mxc Video For Linux 2 driver + * + * @ingroup MXC_V4L2_CAPTURE + */ +#include <linux/version.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/io.h> +#include <linux/semaphore.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/types.h> +#include <linux/fb.h> +#include <linux/dma-mapping.h> +#include <linux/mxcfb.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-int-device.h> +#include "mxc_v4l2_capture.h" +#include "ipu_prp_sw.h" + +static int video_nr = -1; +static cam_data *g_cam; + +/*! This data is used for the output to the display. */ +#define MXC_V4L2_CAPTURE_NUM_OUTPUTS 3 +#define MXC_V4L2_CAPTURE_NUM_INPUTS 2 +static struct v4l2_output mxc_capture_outputs[MXC_V4L2_CAPTURE_NUM_OUTPUTS] = { + { + .index = 0, + .name = "DISP3 BG", + .type = V4L2_OUTPUT_TYPE_ANALOG, + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN, + }, + { + .index = 1, + .name = "DISP0", + .type = V4L2_OUTPUT_TYPE_ANALOG, + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN, + }, + { + .index = 2, + .name = "DISP3 FG", + .type = V4L2_OUTPUT_TYPE_ANALOG, + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN, + }, +}; + +static struct v4l2_input mxc_capture_inputs[MXC_V4L2_CAPTURE_NUM_INPUTS] = { + { + .index = 0, + .name = "CSI IC MEM", + .type = V4L2_INPUT_TYPE_CAMERA, + .audioset = 0, + .tuner = 0, + .std = V4L2_STD_UNKNOWN, + .status = 0, + }, + { + .index = 1, + .name = "CSI MEM", + .type = V4L2_INPUT_TYPE_CAMERA, + .audioset = 0, + .tuner = 0, + .std = V4L2_STD_UNKNOWN, + .status = V4L2_IN_ST_NO_POWER, + }, +}; + +/*! List of TV input video formats supported. The video formats is corresponding + * to the v4l2_id in video_fmt_t. + * Currently, only PAL and NTSC is supported. Needs to be expanded in the + * future. + */ +typedef enum { + TV_NTSC = 0, /*!< Locked on (M) NTSC video signal. */ + TV_PAL, /*!< (B, G, H, I, N)PAL video signal. */ + TV_NOT_LOCKED, /*!< Not locked on a signal. */ +} video_fmt_idx; + +/*! Number of video standards supported (including 'not locked' signal). */ +#define TV_STD_MAX (TV_NOT_LOCKED + 1) + +/*! Video format structure. */ +typedef struct { + int v4l2_id; /*!< Video for linux ID. */ + char name[16]; /*!< Name (e.g., "NTSC", "PAL", etc.) */ + u16 raw_width; /*!< Raw width. */ + u16 raw_height; /*!< Raw height. */ + u16 active_width; /*!< Active width. */ + u16 active_height; /*!< Active height. */ + u16 active_top; /*!< Active top. */ + u16 active_left; /*!< Active left. */ +} video_fmt_t; + +/*! + * Description of video formats supported. + * + * PAL: raw=720x625, active=720x576. + * NTSC: raw=720x525, active=720x480. + */ +static video_fmt_t video_fmts[] = { + { /*! NTSC */ + .v4l2_id = V4L2_STD_NTSC, + .name = "NTSC", + .raw_width = 720, /* SENS_FRM_WIDTH */ + .raw_height = 525, /* SENS_FRM_HEIGHT */ + .active_width = 720, /* ACT_FRM_WIDTH */ + .active_height = 240, /* ACT_FRM_HEIGHT */ + .active_top = 0, + .active_left = 0, + }, + { /*! (B, G, H, I, N) PAL */ + .v4l2_id = V4L2_STD_PAL, + .name = "PAL", + .raw_width = 720, + .raw_height = 625, + .active_width = 720, + .active_height = 288, + .active_top = 0, + .active_left = 0, + }, + { /*! Unlocked standard */ + .v4l2_id = V4L2_STD_ALL, + .name = "Autodetect", + .raw_width = 720, + .raw_height = 625, + .active_width = 720, + .active_height = 288, + .active_top = 0, + .active_left = 0, + }, +}; + +/*!* Standard index of TV. */ +static video_fmt_idx video_index = TV_NOT_LOCKED; + +static int mxc_v4l2_master_attach(struct v4l2_int_device *slave); +static void mxc_v4l2_master_detach(struct v4l2_int_device *slave); +static u8 camera_power(cam_data *cam, bool cameraOn); +static int start_preview(cam_data *cam); +static int stop_preview(cam_data *cam); + +/*! Information about this driver. */ +static struct v4l2_int_master mxc_v4l2_master = { + .attach = mxc_v4l2_master_attach, + .detach = mxc_v4l2_master_detach, +}; + +static struct v4l2_int_device mxc_v4l2_int_device = { + .module = THIS_MODULE, + .name = "mxc_v4l2_cap", + .type = v4l2_int_type_master, + .u = { + .master = &mxc_v4l2_master, + }, +}; + +/*************************************************************************** + * Functions for handling Frame buffers. + **************************************************************************/ + +/*! + * Free frame buffers + * + * @param cam Structure cam_data * + * + * @return status 0 success. + */ +static int mxc_free_frame_buf(cam_data *cam) +{ + int i; + + pr_debug("MVC: In mxc_free_frame_buf\n"); + + for (i = 0; i < FRAME_NUM; i++) { + if (cam->frame[i].vaddress != 0) { + dma_free_coherent(0, cam->frame[i].buffer.length, + cam->frame[i].vaddress, + cam->frame[i].paddress); + cam->frame[i].vaddress = 0; + } + } + + return 0; +} + +/*! + * Allocate frame buffers + * + * @param cam Structure cam_data* + * @param count int number of buffer need to allocated + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_frame_buf(cam_data *cam, int count) +{ + int i; + + pr_debug("In MVC:mxc_allocate_frame_buf - size=%d\n", + cam->v2f.fmt.pix.sizeimage); + + for (i = 0; i < count; i++) { + cam->frame[i].vaddress = + dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->frame[i].paddress, + GFP_DMA | GFP_KERNEL); + if (cam->frame[i].vaddress == 0) { + pr_err("ERROR: v4l2 capture: " + "mxc_allocate_frame_buf failed.\n"); + mxc_free_frame_buf(cam); + return -ENOBUFS; + } + cam->frame[i].buffer.index = i; + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + cam->frame[i].buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->frame[i].buffer.length = + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); + cam->frame[i].buffer.memory = V4L2_MEMORY_MMAP; + cam->frame[i].buffer.m.offset = cam->frame[i].paddress; + cam->frame[i].index = i; + } + + return 0; +} + +/*! + * Free frame buffers status + * + * @param cam Structure cam_data * + * + * @return none + */ +static void mxc_free_frames(cam_data *cam) +{ + int i; + + pr_debug("In MVC:mxc_free_frames\n"); + + for (i = 0; i < FRAME_NUM; i++) { + cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; + } + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); +} + +/*! + * Return the buffer status + * + * @param cam Structure cam_data * + * @param buf Structure v4l2_buffer * + * + * @return status 0 success, EINVAL failed. + */ +static int mxc_v4l2_buffer_status(cam_data *cam, struct v4l2_buffer *buf) +{ + pr_debug("In MVC:mxc_v4l2_buffer_status\n"); + + if (buf->index < 0 || buf->index >= FRAME_NUM) { + pr_err("ERROR: v4l2 capture: mxc_v4l2_buffer_status buffers " + "not allocated\n"); + return -EINVAL; + } + + memcpy(buf, &(cam->frame[buf->index].buffer), sizeof(*buf)); + return 0; +} + +/*************************************************************************** + * Functions for handling the video stream. + **************************************************************************/ + +/*! + * Indicates whether the palette is supported. + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return ((palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_BGR24) || + (palette == V4L2_PIX_FMT_RGB24) || + (palette == V4L2_PIX_FMT_BGR32) || + (palette == V4L2_PIX_FMT_RGB32) || + (palette == V4L2_PIX_FMT_YUV422P) || + (palette == V4L2_PIX_FMT_UYVY) || + (palette == V4L2_PIX_FMT_YUYV) || + (palette == V4L2_PIX_FMT_YUV420) || + (palette == V4L2_PIX_FMT_NV12)); +} + +/*! + * Start the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamon(cam_data *cam) +{ + struct mxc_v4l_frame *frame; + int err = 0; + + pr_debug("In MVC:mxc_streamon\n"); + + if (NULL == cam) { + pr_err("ERROR! cam parameter is NULL\n"); + return -1; + } + + if (cam->capture_on) { + pr_err("ERROR: v4l2 capture: Capture stream has been turned " + " on\n"); + return -1; + } + + if (list_empty(&cam->ready_q)) { + pr_err("ERROR: v4l2 capture: mxc_streamon buffer has not been " + "queued yet\n"); + return -EINVAL; + } + + cam->capture_pid = current->pid; + + if (cam->overlay_on == true) + stop_preview(cam); + + if (cam->enc_enable) { + err = cam->enc_enable(cam); + if (err != 0) { + return err; + } + } + + cam->ping_pong_csi = 0; + if (cam->enc_update_eba) { + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + err = cam->enc_update_eba(frame->buffer.m.offset, + &cam->ping_pong_csi); + + frame = + list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); + list_del(cam->ready_q.next); + list_add_tail(&frame->queue, &cam->working_q); + err |= cam->enc_update_eba(frame->buffer.m.offset, + &cam->ping_pong_csi); + } else { + return -EINVAL; + } + + if (cam->overlay_on == true) + start_preview(cam); + + if (cam->enc_enable_csi) { + err = cam->enc_enable_csi(cam); + if (err != 0) + return err; + } + + cam->capture_on = true; + + return err; +} + +/*! + * Shut down the encoder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int mxc_streamoff(cam_data *cam) +{ + int err = 0; + + pr_debug("In MVC:mxc_streamoff\n"); + + if (cam->capture_on == false) + return 0; + + if (cam->enc_disable_csi) { + err = cam->enc_disable_csi(cam); + if (err != 0) + return err; + } + if (cam->enc_disable) + err = cam->enc_disable(cam); + + mxc_free_frames(cam); + mxc_capture_inputs[cam->current_input].status |= V4L2_IN_ST_NO_POWER; + cam->capture_on = false; + return err; +} + +/*! + * Valid and adjust the overlay window size, position + * + * @param cam structure cam_data * + * @param win struct v4l2_window * + * + * @return 0 + */ +static int verify_preview(cam_data *cam, struct v4l2_window *win) +{ + int i = 0, width_bound = 0, height_bound = 0; + int *width, *height; + struct fb_info *bg_fbi = NULL; + bool foregound_fb; + + pr_debug("In MVC: verify_preview\n"); + + do { + cam->overlay_fb = (struct fb_info *)registered_fb[i]; + if (cam->overlay_fb == NULL) { + pr_err("ERROR: verify_preview frame buffer NULL.\n"); + return -1; + } + if (strcmp(cam->overlay_fb->fix.id, "DISP3 BG") == 0) + bg_fbi = cam->overlay_fb; + if (strcmp(cam->overlay_fb->fix.id, + mxc_capture_outputs[cam->output].name) == 0) { + if (strcmp(cam->overlay_fb->fix.id, "DISP3 FG") == 0) + foregound_fb = true; + break; + } + } while (++i < FB_MAX); + + if (foregound_fb) { + width_bound = bg_fbi->var.xres; + height_bound = bg_fbi->var.yres; + + if (win->w.width + win->w.left > bg_fbi->var.xres || + win->w.height + win->w.top > bg_fbi->var.yres) { + pr_err("ERROR: FG window position exceeds.\n"); + return -1; + } + } else { + /* 4 bytes alignment for BG */ + width_bound = cam->overlay_fb->var.xres; + height_bound = cam->overlay_fb->var.yres; + + if (cam->overlay_fb->var.bits_per_pixel == 24) { + win->w.left -= win->w.left % 4; + } else if (cam->overlay_fb->var.bits_per_pixel == 16) { + win->w.left -= win->w.left % 2; + } + + if (win->w.width + win->w.left > cam->overlay_fb->var.xres) + win->w.width = cam->overlay_fb->var.xres - win->w.left; + if (win->w.height + win->w.top > cam->overlay_fb->var.yres) + win->w.height = cam->overlay_fb->var.yres - win->w.top; + } + + /* stride line limitation */ + win->w.height -= win->w.height % 8; + win->w.width -= win->w.width % 8; + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + height = &win->w.width; + width = &win->w.height; + } else { + width = &win->w.width; + height = &win->w.height; + } + + if ((cam->crop_bounds.width / *width > 8) || + ((cam->crop_bounds.width / *width == 8) && + (cam->crop_bounds.width % *width))) { + *width = cam->crop_bounds.width / 8; + if (*width % 8) + *width += 8 - *width % 8; + if (*width + win->w.left > width_bound) { + pr_err("ERROR: v4l2 capture: width exceeds " + "resize limit.\n"); + return -1; + } + pr_err("ERROR: v4l2 capture: width exceeds limit. " + "Resize to %d.\n", + *width); + } + + if ((cam->crop_bounds.height / *height > 8) || + ((cam->crop_bounds.height / *height == 8) && + (cam->crop_bounds.height % *height))) { + *height = cam->crop_bounds.height / 8; + if (*height % 8) + *height += 8 - *height % 8; + if (*height + win->w.top > height_bound) { + pr_err("ERROR: v4l2 capture: height exceeds " + "resize limit.\n"); + return -1; + } + pr_err("ERROR: v4l2 capture: height exceeds limit " + "resize to %d.\n", + *height); + } + + return 0; +} + +/*! + * start the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int start_preview(cam_data *cam) +{ + int err = 0; + + pr_debug("MVC: start_preview\n"); + +#if defined(CONFIG_MXC_IPU_PRP_VF_SDC) || defined(CONFIG_MXC_IPU_PRP_VF_SDC_MODULE) + pr_debug(" This is an SDC display\n"); + if (cam->output == 0 || cam->output == 2) { + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) + err = prp_vf_sdc_select(cam); + else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_PRIMARY) + err = prp_vf_sdc_select_bg(cam); + if (err != 0) + return err; + + err = cam->vf_start_sdc(cam); + if (err != 0) + return err; + + if (cam->vf_enable_csi) + err = cam->vf_enable_csi(cam); + } +#endif + +#if defined(CONFIG_MXC_IPU_PRP_VF_ADC) || defined(CONFIG_MXC_IPU_PRP_VF_ADC_MODULE) + pr_debug(" This is an ADC display\n"); + if (cam->output == 1) { + err = prp_vf_adc_select(cam); + if (err != 0) + return err; + + err = cam->vf_start_adc(cam); + } +#endif + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + return err; +} + +/*! + * shut down the viewfinder job + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static int stop_preview(cam_data *cam) +{ + int err = 0; + + pr_debug("MVC: stop preview\n"); + +#if defined(CONFIG_MXC_IPU_PRP_VF_ADC) || defined(CONFIG_MXC_IPU_PRP_VF_ADC_MODULE) + if (cam->output == 1) { + err = prp_vf_adc_deselect(cam); + } +#endif + +#if defined(CONFIG_MXC_IPU_PRP_VF_SDC) || defined(CONFIG_MXC_IPU_PRP_VF_SDC_MODULE) + if (cam->vf_disable_csi) { + err = cam->vf_disable_csi(cam); + if (err != 0) + return err; + } + + if (cam->output == 0 || cam->output == 2) { + if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) + err = prp_vf_sdc_deselect(cam); + else if (cam->v4l2_fb.flags == V4L2_FBUF_FLAG_PRIMARY) + err = prp_vf_sdc_deselect_bg(cam); + } +#endif + + return err; +} + +/*************************************************************************** + * VIDIOC Functions. + **************************************************************************/ + +/*! + * V4L2 - mxc_v4l2_g_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + + pr_debug("In MVC: mxc_v4l2_g_fmt type=%d\n", f->type); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + f->fmt.pix = cam->v2f.fmt.pix; + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_OVERLAY\n"); + f->fmt.win = cam->win; + break; + default: + pr_debug(" type is invalid\n"); + retval = -EINVAL; + } + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + return retval; +} + +/*! + * V4L2 - mxc_v4l2_s_fmt function + * + * @param cam structure cam_data * + * + * @param f structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_fmt(cam_data *cam, struct v4l2_format *f) +{ + int retval = 0; + int size = 0; + int bytesperline = 0; + int *width, *height; + + pr_debug("In MVC: mxc_v4l2_s_fmt\n"); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type=V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_err("ERROR: v4l2 capture: mxc_v4l2_s_fmt: format " + "not supported\n"); + return -EINVAL; + } + + /* + * Force the capture window resolution to be crop bounds + * for CSI MEM input mode. + */ + if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI MEM") == 0) { + f->fmt.pix.width = cam->crop_current.width; + f->fmt.pix.height = cam->crop_current.height; + } + + if (cam->rotation >= IPU_ROTATE_90_RIGHT) { + height = &f->fmt.pix.width; + width = &f->fmt.pix.height; + } else { + width = &f->fmt.pix.width; + height = &f->fmt.pix.height; + } + + /* stride line limitation */ + *width -= *width % 8; + *height -= *height % 8; + + if ((cam->crop_current.width / *width > 8) || + ((cam->crop_current.width / *width == 8) && + (cam->crop_current.width % *width))) { + *width = cam->crop_current.width / 8; + if (*width % 8) + *width += 8 - *width % 8; + pr_err("ERROR: v4l2 capture: width exceeds limit " + "resize to %d.\n", + *width); + } + + if ((cam->crop_current.height / *height > 8) || + ((cam->crop_current.height / *height == 8) && + (cam->crop_current.height % *height))) { + *height = cam->crop_current.height / 8; + if (*height % 8) + *height += 8 - *height % 8; + pr_err("ERROR: v4l2 capture: height exceeds limit " + "resize to %d.\n", + *height); + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_RGB565: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_BGR24: + size = f->fmt.pix.width * f->fmt.pix.height * 3; + bytesperline = f->fmt.pix.width * 3; + break; + case V4L2_PIX_FMT_RGB24: + size = f->fmt.pix.width * f->fmt.pix.height * 3; + bytesperline = f->fmt.pix.width * 3; + break; + case V4L2_PIX_FMT_BGR32: + size = f->fmt.pix.width * f->fmt.pix.height * 4; + bytesperline = f->fmt.pix.width * 4; + break; + case V4L2_PIX_FMT_RGB32: + size = f->fmt.pix.width * f->fmt.pix.height * 4; + bytesperline = f->fmt.pix.width * 4; + break; + case V4L2_PIX_FMT_YUV422P: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width; + break; + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + size = f->fmt.pix.width * f->fmt.pix.height * 2; + bytesperline = f->fmt.pix.width * 2; + break; + case V4L2_PIX_FMT_YUV420: + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + bytesperline = f->fmt.pix.width; + break; + case V4L2_PIX_FMT_NV12: + size = f->fmt.pix.width * f->fmt.pix.height * 3 / 2; + bytesperline = f->fmt.pix.width; + break; + default: + break; + } + + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + if (f->fmt.pix.sizeimage < size) { + f->fmt.pix.sizeimage = size; + } else { + size = f->fmt.pix.sizeimage; + } + + cam->v2f.fmt.pix = f->fmt.pix; + + if (cam->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&cam->offset, + (void *)cam->v2f.fmt.pix.priv, + sizeof(cam->offset))) { + retval = -EFAULT; + break; + } + } + break; + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + pr_debug(" type=V4L2_BUF_TYPE_VIDEO_OVERLAY\n"); + retval = verify_preview(cam, &f->fmt.win); + cam->win = f->fmt.win; + break; + default: + retval = -EINVAL; + } + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + return retval; +} + +/*! + * get control param + * + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_ctrl(cam_data *cam, struct v4l2_control *c) +{ + int status = 0; + + pr_debug("In MVC:mxc_v4l2_g_ctrl\n"); + + /* probably don't need to store the values that can be retrieved, + * locally, but they are for now. */ + switch (c->id) { + case V4L2_CID_HFLIP: + /* This is handled in the ipu. */ + if (cam->rotation == IPU_ROTATE_HORIZ_FLIP) + c->value = 1; + break; + case V4L2_CID_VFLIP: + /* This is handled in the ipu. */ + if (cam->rotation == IPU_ROTATE_VERT_FLIP) + c->value = 1; + break; + case V4L2_CID_MXC_ROT: + /* This is handled in the ipu. */ + c->value = cam->rotation; + break; + case V4L2_CID_BRIGHTNESS: + c->value = cam->bright; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->bright = c->value; + break; + case V4L2_CID_HUE: + c->value = cam->hue; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->hue = c->value; + break; + case V4L2_CID_CONTRAST: + c->value = cam->contrast; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->contrast = c->value; + break; + case V4L2_CID_SATURATION: + c->value = cam->saturation; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->saturation = c->value; + break; + case V4L2_CID_RED_BALANCE: + c->value = cam->red; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->red = c->value; + break; + case V4L2_CID_BLUE_BALANCE: + c->value = cam->blue; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->blue = c->value; + break; + case V4L2_CID_BLACK_LEVEL: + c->value = cam->ae_mode; + status = vidioc_int_g_ctrl(cam->sensor, c); + cam->ae_mode = c->value; + break; + default: + status = vidioc_int_g_ctrl(cam->sensor, c); + } + + return status; +} + +/*! + * V4L2 - set_control function + * V4L2_CID_PRIVATE_BASE is the extention for IPU preprocessing. + * 0 for normal operation + * 1 for vertical flip + * 2 for horizontal flip + * 3 for horizontal and vertical flip + * 4 for 90 degree rotation + * @param cam structure cam_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_ctrl(cam_data *cam, struct v4l2_control *c) +{ + int ret = 0; + int tmp_rotation = IPU_ROTATE_NONE; + + pr_debug("In MVC:mxc_v4l2_s_ctrl\n"); + + switch (c->id) { + case V4L2_CID_HFLIP: + /* This is done by the IPU */ + if (c->value == 1) { + if ((cam->rotation != IPU_ROTATE_VERT_FLIP) && + (cam->rotation != IPU_ROTATE_180)) + cam->rotation = IPU_ROTATE_HORIZ_FLIP; + else + cam->rotation = IPU_ROTATE_180; + } else { + if (cam->rotation == IPU_ROTATE_HORIZ_FLIP) + cam->rotation = IPU_ROTATE_NONE; + if (cam->rotation == IPU_ROTATE_180) + cam->rotation = IPU_ROTATE_VERT_FLIP; + } + break; + case V4L2_CID_VFLIP: + /* This is done by the IPU */ + if (c->value == 1) { + if ((cam->rotation != IPU_ROTATE_HORIZ_FLIP) && + (cam->rotation != IPU_ROTATE_180)) + cam->rotation = IPU_ROTATE_VERT_FLIP; + else + cam->rotation = IPU_ROTATE_180; + } else { + if (cam->rotation == IPU_ROTATE_VERT_FLIP) + cam->rotation = IPU_ROTATE_NONE; + if (cam->rotation == IPU_ROTATE_180) + cam->rotation = IPU_ROTATE_HORIZ_FLIP; + } + break; + case V4L2_CID_MXC_ROT: + case V4L2_CID_MXC_VF_ROT: + /* This is done by the IPU */ + switch (c->value) { + case V4L2_MXC_ROTATE_NONE: + tmp_rotation = IPU_ROTATE_NONE; + break; + case V4L2_MXC_ROTATE_VERT_FLIP: + tmp_rotation = IPU_ROTATE_VERT_FLIP; + break; + case V4L2_MXC_ROTATE_HORIZ_FLIP: + tmp_rotation = IPU_ROTATE_HORIZ_FLIP; + break; + case V4L2_MXC_ROTATE_180: + tmp_rotation = IPU_ROTATE_180; + break; + case V4L2_MXC_ROTATE_90_RIGHT: + tmp_rotation = IPU_ROTATE_90_RIGHT; + break; + case V4L2_MXC_ROTATE_90_RIGHT_VFLIP: + tmp_rotation = IPU_ROTATE_90_RIGHT_VFLIP; + break; + case V4L2_MXC_ROTATE_90_RIGHT_HFLIP: + tmp_rotation = IPU_ROTATE_90_RIGHT_HFLIP; + break; + case V4L2_MXC_ROTATE_90_LEFT: + tmp_rotation = IPU_ROTATE_90_LEFT; + break; + default: + ret = -EINVAL; + } + + if (c->id == V4L2_CID_MXC_VF_ROT) + cam->vf_rotation = tmp_rotation; + else + cam->rotation = tmp_rotation; + + break; + case V4L2_CID_HUE: + cam->hue = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_CONTRAST: + cam->contrast = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_BRIGHTNESS: + cam->bright = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_SATURATION: + cam->saturation = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_RED_BALANCE: + cam->red = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_BLUE_BALANCE: + cam->blue = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_EXPOSURE: + cam->ae_mode = c->value; + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + ret = vidioc_int_s_ctrl(cam->sensor, c); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + break; + case V4L2_CID_MXC_FLASH: +#ifdef CONFIG_MXC_IPU_V1 + ipu_csi_flash_strobe(true); +#endif + break; + default: + pr_debug(" default case\n"); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * V4L2 - mxc_v4l2_s_param function + * Allows setting of capturemode and frame rate. + * + * @param cam structure cam_data * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_param(cam_data *cam, struct v4l2_streamparm *parm) +{ + struct v4l2_ifparm ifparm; + struct v4l2_format cam_fmt; + struct v4l2_streamparm currentparm; + ipu_csi_signal_cfg_t csi_param; + int err = 0; + + pr_debug("In mxc_v4l2_s_param\n"); + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_err(KERN_ERR "mxc_v4l2_s_param invalid type\n"); + return -EINVAL; + } + + /* Stop the viewfinder */ + if (cam->overlay_on == true) { + stop_preview(cam); + } + + currentparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* First check that this device can support the changes requested. */ + err = vidioc_int_g_parm(cam->sensor, ¤tparm); + if (err) { + pr_err("%s: vidioc_int_g_parm returned an error %d\n", + __func__, err); + goto exit; + } + + pr_debug(" Current capabilities are %x\n", + currentparm.parm.capture.capability); + pr_debug(" Current capturemode is %d change to %d\n", + currentparm.parm.capture.capturemode, + parm->parm.capture.capturemode); + pr_debug(" Current framerate is %d change to %d\n", + currentparm.parm.capture.timeperframe.denominator, + parm->parm.capture.timeperframe.denominator); + + /* This will change any camera settings needed. */ + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + err = vidioc_int_s_parm(cam->sensor, parm); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + if (err) { + pr_err("%s: vidioc_int_s_parm returned an error %d\n", + __func__, err); + goto exit; + } + + /* If resolution changed, need to re-program the CSI */ + /* Get new values. */ + vidioc_int_g_ifparm(cam->sensor, &ifparm); + + csi_param.data_width = 0; + csi_param.clk_mode = 0; + csi_param.ext_vsync = 0; + csi_param.Vsync_pol = 0; + csi_param.Hsync_pol = 0; + csi_param.pixclk_pol = 0; + csi_param.data_pol = 0; + csi_param.sens_clksrc = 0; + csi_param.pack_tight = 0; + csi_param.force_eof = 0; + csi_param.data_en_pol = 0; + csi_param.data_fmt = 0; + csi_param.csi = 0; + csi_param.mclk = 0; + + /* This may not work on other platforms. Check when adding a new one.*/ + pr_debug(" clock_curr=mclk=%d\n", ifparm.u.bt656.clock_curr); + if (ifparm.u.bt656.clock_curr == 0) { + csi_param.clk_mode = IPU_CSI_CLK_MODE_CCIR656_INTERLACED; + } else { + csi_param.clk_mode = IPU_CSI_CLK_MODE_GATED_CLK; + } + + csi_param.pixclk_pol = ifparm.u.bt656.latch_clk_inv; + + if (ifparm.u.bt656.mode == V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT) { + csi_param.data_width = IPU_CSI_DATA_WIDTH_8; + } else if (ifparm.u.bt656.mode + == V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT) { + csi_param.data_width = IPU_CSI_DATA_WIDTH_10; + } else { + csi_param.data_width = IPU_CSI_DATA_WIDTH_8; + } + + csi_param.Vsync_pol = ifparm.u.bt656.nobt_vs_inv; + csi_param.Hsync_pol = ifparm.u.bt656.nobt_hs_inv; + csi_param.ext_vsync = ifparm.u.bt656.bt_sync_correct; + + /* if the capturemode changed, the size bounds will have changed. */ + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vidioc_int_g_fmt_cap(cam->sensor, &cam_fmt); + pr_debug(" g_fmt_cap returns widthxheight of input as %d x %d\n", + cam_fmt.fmt.pix.width, cam_fmt.fmt.pix.height); + + csi_param.data_fmt = cam_fmt.fmt.pix.pixelformat; + + cam->crop_bounds.top = cam->crop_bounds.left = 0; + cam->crop_bounds.width = cam_fmt.fmt.pix.width; + cam->crop_bounds.height = cam_fmt.fmt.pix.height; + + /* + * Set the default current cropped resolution to be the same with + * the cropping boundary(except for tvin module). + */ + if (cam->device_type != 1) { + cam->crop_current.width = cam->crop_bounds.width; + cam->crop_current.height = cam->crop_bounds.height; + } + + /* This essentially loses the data at the left and bottom of the image + * giving a digital zoom image, if crop_current is less than the full + * size of the image. */ + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.height, cam->csi); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top, + cam->csi); + ipu_csi_init_interface(cam->crop_bounds.width, + cam->crop_bounds.height, + cam_fmt.fmt.pix.pixelformat, csi_param); + + +exit: + if (cam->overlay_on == true) + start_preview(cam); + + return err; +} + +/*! + * V4L2 - mxc_v4l2_s_std function + * + * Sets the TV standard to be used. + * + * @param cam structure cam_data * + * @param parm structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_s_std(cam_data *cam, v4l2_std_id e) +{ + bool change = false; + + if (e != cam->standard.id) { + change = true; + } + + pr_debug("In mxc_v4l2_s_std %Lx\n", e); + if (e == V4L2_STD_PAL) { + pr_debug(" Setting standard to PAL %Lx\n", V4L2_STD_PAL); + cam->standard.id = V4L2_STD_PAL; + video_index = TV_PAL; + cam->crop_current.top = 0; + } else if (e == V4L2_STD_NTSC) { + pr_debug(" Setting standard to NTSC %Lx\n", + V4L2_STD_NTSC); + /* Get rid of the white dot line in NTSC signal input */ + cam->standard.id = V4L2_STD_NTSC; + video_index = TV_NTSC; + cam->crop_current.top = 12; + } else { + cam->standard.id = V4L2_STD_ALL; + video_index = TV_NOT_LOCKED; + cam->crop_current.top = 0; + pr_err("ERROR: unrecognized std! %Lx (PAL=%Lx, NTSC=%Lx\n", + e, V4L2_STD_PAL, V4L2_STD_NTSC); + } + + cam->standard.index = video_index; + strcpy(cam->standard.name, video_fmts[video_index].name); + cam->crop_bounds.width = video_fmts[video_index].raw_width; + cam->crop_bounds.height = video_fmts[video_index].raw_height; + cam->crop_current.width = video_fmts[video_index].active_width; + cam->crop_current.height = video_fmts[video_index].active_height; + cam->crop_current.left = 0; + + return 0; +} + +/*! + * V4L2 - mxc_v4l2_g_std function + * + * Gets the TV standard from the TV input device. + * + * @param cam structure cam_data * + * + * @param e structure v4l2_streamparm * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2_g_std(cam_data *cam, v4l2_std_id *e) +{ + struct v4l2_format tv_fmt; + + pr_debug("In mxc_v4l2_g_std\n"); + + if (cam->device_type == 1) { + /* Use this function to get what the TV-In device detects the + * format to be. pixelformat is used to return the std value + * since the interface has no vidioc_g_std.*/ + tv_fmt.type = V4L2_BUF_TYPE_PRIVATE; + vidioc_int_g_fmt_cap(cam->sensor, &tv_fmt); + + /* If the TV-in automatically detects the standard, then if it + * changes, the settings need to change. */ + if (cam->standard_autodetect) { + if (cam->standard.id != tv_fmt.fmt.pix.pixelformat) { + pr_debug("MVC: mxc_v4l2_g_std: " + "Changing standard\n"); + mxc_v4l2_s_std(cam, tv_fmt.fmt.pix.pixelformat); + } + } + + *e = tv_fmt.fmt.pix.pixelformat; + } + + return 0; +} + +/*! + * Dequeue one V4L capture buffer + * + * @param cam structure cam_data * + * @param buf structure v4l2_buffer * + * + * @return status 0 success, EINVAL invalid frame number, + * ETIME timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_dqueue(cam_data *cam, struct v4l2_buffer *buf) +{ + int retval = 0; + struct mxc_v4l_frame *frame; + unsigned long lock_flags; + + pr_debug("In MVC:mxc_v4l_dqueue\n"); + + if (!wait_event_interruptible_timeout(cam->enc_queue, + cam->enc_counter != 0, 10 * HZ)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_dqueue timeout " + "enc_counter %x\n", + cam->enc_counter); + return -ETIME; + } else if (signal_pending(current)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_dqueue() " + "interrupt received\n"); + return -ERESTARTSYS; + } + + spin_lock_irqsave(&cam->dqueue_int_lock, lock_flags); + + cam->enc_counter--; + + frame = list_entry(cam->done_q.next, struct mxc_v4l_frame, queue); + list_del(cam->done_q.next); + if (frame->buffer.flags & V4L2_BUF_FLAG_DONE) { + frame->buffer.flags &= ~V4L2_BUF_FLAG_DONE; + } else if (frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + pr_err("ERROR: v4l2 capture: VIDIOC_DQBUF: " + "Buffer not filled.\n"); + frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } else if ((frame->buffer.flags & 0x7) == V4L2_BUF_FLAG_MAPPED) { + pr_err("ERROR: v4l2 capture: VIDIOC_DQBUF: " + "Buffer not queued.\n"); + retval = -EINVAL; + } + + buf->bytesused = cam->v2f.fmt.pix.sizeimage; + buf->index = frame->index; + buf->flags = frame->buffer.flags; + buf->m = cam->frame[frame->index].buffer.m; + buf->timestamp = cam->frame[frame->index].buffer.timestamp; + + spin_unlock_irqrestore(&cam->dqueue_int_lock, lock_flags); + return retval; +} + +/*! + * V4L interface - open function + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l_open(struct file *file) +{ + struct v4l2_ifparm ifparm; + struct v4l2_format cam_fmt; + ipu_csi_signal_cfg_t csi_param; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int err = 0; + + pr_debug("\nIn MVC: mxc_v4l_open\n"); + pr_debug(" device name is %s\n", dev->name); + + if (!cam) { + pr_err("ERROR: v4l2 capture: Internal error, " + "cam_data not found!\n"); + return -EBADF; + } + + down(&cam->busy_lock); + err = 0; + if (signal_pending(current)) + goto oops; + + if (cam->open_count++ == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + + if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI MEM") == 0) { +#if defined(CONFIG_MXC_IPU_CSI_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) + err = csi_enc_select(cam); +#endif + } else if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI IC MEM") == 0) { +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) + err = prp_enc_select(cam); +#endif + } + + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + vidioc_int_g_ifparm(cam->sensor, &ifparm); + + csi_param.sens_clksrc = 0; + + csi_param.clk_mode = 0; + csi_param.data_pol = 0; + csi_param.ext_vsync = 0; + + csi_param.pack_tight = 0; + csi_param.force_eof = 0; + csi_param.data_en_pol = 0; + csi_param.mclk = ifparm.u.bt656.clock_curr; + + csi_param.pixclk_pol = ifparm.u.bt656.latch_clk_inv; + + /* Once we handle multiple inputs this will need to change. */ + csi_param.csi = 0; + + if (ifparm.u.bt656.mode + == V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT) + csi_param.data_width = IPU_CSI_DATA_WIDTH_8; + else if (ifparm.u.bt656.mode + == V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT) + csi_param.data_width = IPU_CSI_DATA_WIDTH_10; + else + csi_param.data_width = IPU_CSI_DATA_WIDTH_8; + + + csi_param.Vsync_pol = ifparm.u.bt656.nobt_vs_inv; + csi_param.Hsync_pol = ifparm.u.bt656.nobt_hs_inv; + + csi_param.csi = cam->csi; + + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vidioc_int_g_fmt_cap(cam->sensor, &cam_fmt); + + /* Reset the sizes. Needed to prevent carryover of last + * operation.*/ + cam->crop_bounds.top = cam->crop_bounds.left = 0; + cam->crop_bounds.width = cam_fmt.fmt.pix.width; + cam->crop_bounds.height = cam_fmt.fmt.pix.height; + + /* This also is the max crop size for this device. */ + cam->crop_defrect.top = cam->crop_defrect.left = 0; + cam->crop_defrect.width = cam_fmt.fmt.pix.width; + cam->crop_defrect.height = cam_fmt.fmt.pix.height; + + /* At this point, this is also the current image size. */ + cam->crop_current.top = cam->crop_current.left = 0; + cam->crop_current.width = cam_fmt.fmt.pix.width; + cam->crop_current.height = cam_fmt.fmt.pix.height; + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + csi_param.data_fmt = cam_fmt.fmt.pix.pixelformat; + pr_debug("On Open: Input to ipu size is %d x %d\n", + cam_fmt.fmt.pix.width, cam_fmt.fmt.pix.height); + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.width, + cam->csi); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top, + cam->csi); + ipu_csi_init_interface(cam->crop_bounds.width, + cam->crop_bounds.height, + cam_fmt.fmt.pix.pixelformat, + csi_param); + + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + true, true); + vidioc_int_init(cam->sensor); + + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, + false, false); +} + + file->private_data = dev; + + oops: + up(&cam->busy_lock); + return err; +} + +/*! + * V4L interface - close function + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l_close(struct file *file) +{ + struct video_device *dev = video_devdata(file); + int err = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("In MVC:mxc_v4l_close\n"); + + if (!cam) { + pr_err("ERROR: v4l2 capture: Internal error, " + "cam_data not found!\n"); + return -EBADF; + } + + /* for the case somebody hit the ctrl C */ + if (cam->overlay_pid == current->pid) { + err = stop_preview(cam); + cam->overlay_on = false; + } + if (cam->capture_pid == current->pid) { + err |= mxc_streamoff(cam); + wake_up_interruptible(&cam->enc_queue); + } + + if (--cam->open_count == 0) { + wait_event_interruptible(cam->power_queue, + cam->low_power == false); + pr_info("mxc_v4l_close: release resource\n"); + + if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI MEM") == 0) { +#if defined(CONFIG_MXC_IPU_CSI_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) + err |= csi_enc_deselect(cam); +#endif + } else if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI IC MEM") == 0) { +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) + err |= prp_enc_deselect(cam); +#endif + } + + mxc_free_frame_buf(cam); + file->private_data = NULL; + + /* capture off */ + wake_up_interruptible(&cam->enc_queue); + mxc_free_frames(cam); + cam->enc_counter++; + } + + return err; +} + +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC) || \ + defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) || \ + defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) +/* + * V4L interface - read function + * + * @param file struct file * + * @param read buf char * + * @param count size_t + * @param ppos structure loff_t * + * + * @return bytes read + */ +static ssize_t mxc_v4l_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + int err = 0; + u8 *v_address[2]; + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + /* Stop the viewfinder */ + if (cam->overlay_on == true) + stop_preview(cam); + + v_address[0] = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->still_buf[0], + GFP_DMA | GFP_KERNEL); + + v_address[1] = dma_alloc_coherent(0, + PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), + &cam->still_buf[1], + GFP_DMA | GFP_KERNEL); + + if (!v_address[0] || !v_address[1]) { + err = -ENOBUFS; + goto exit0; + } + + err = prp_still_select(cam); + if (err != 0) { + err = -EIO; + goto exit0; + } + + cam->still_counter = 0; + err = cam->csi_start(cam); + if (err != 0) { + err = -EIO; + goto exit1; + } + + if (!wait_event_interruptible_timeout(cam->still_queue, + cam->still_counter != 0, + 10 * HZ)) { + pr_err("ERROR: v4l2 capture: mxc_v4l_read timeout counter %x\n", + cam->still_counter); + err = -ETIME; + goto exit1; + } + err = copy_to_user(buf, v_address[1], cam->v2f.fmt.pix.sizeimage); + + exit1: + prp_still_deselect(cam); + + exit0: + if (v_address[0] != 0) + dma_free_coherent(0, cam->v2f.fmt.pix.sizeimage, v_address[0], + cam->still_buf[0]); + if (v_address[1] != 0) + dma_free_coherent(0, cam->v2f.fmt.pix.sizeimage, v_address[1], + cam->still_buf[1]); + + cam->still_buf[0] = cam->still_buf[1] = 0; + + if (cam->overlay_on == true) { + start_preview(cam); + } + + up(&cam->busy_lock); + if (err < 0) + return err; + + return (cam->v2f.fmt.pix.sizeimage - err); +} +#endif + +/*! + * V4L interface - ioctl function + * + * @param file struct file* + * + * @param ioctlnr unsigned int + * + * @param arg void* + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static long mxc_v4l_do_ioctl(struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + int retval = 0; + unsigned long lock_flags; + + pr_debug("In MVC: mxc_v4l_do_ioctl %x\n", ioctlnr); + wait_event_interruptible(cam->power_queue, cam->low_power == false); + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + /*! + * V4l2 VIDIOC_QUERYCAP ioctl + */ + case VIDIOC_QUERYCAP: { + struct v4l2_capability *cap = arg; + pr_debug(" case VIDIOC_QUERYCAP\n"); + strcpy(cap->driver, "mxc_v4l2"); + cap->version = KERNEL_VERSION(0, 1, 11); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + break; + } + + /*! + * V4l2 VIDIOC_G_FMT ioctl + */ + case VIDIOC_G_FMT: { + struct v4l2_format *gf = arg; + pr_debug(" case VIDIOC_G_FMT\n"); + retval = mxc_v4l2_g_fmt(cam, gf); + break; + } + + /*! + * V4l2 VIDIOC_S_FMT ioctl + */ + case VIDIOC_S_FMT: { + struct v4l2_format *sf = arg; + pr_debug(" case VIDIOC_S_FMT\n"); + retval = mxc_v4l2_s_fmt(cam, sf); + break; + } + + /*! + * V4l2 VIDIOC_REQBUFS ioctl + */ + case VIDIOC_REQBUFS: { + struct v4l2_requestbuffers *req = arg; + pr_debug(" case VIDIOC_REQBUFS\n"); + + if (req->count > FRAME_NUM) { + pr_err("ERROR: v4l2 capture: VIDIOC_REQBUFS: " + "not enough buffers\n"); + req->count = FRAME_NUM; + } + + if ((req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (req->memory != V4L2_MEMORY_MMAP)) { + pr_err("ERROR: v4l2 capture: VIDIOC_REQBUFS: " + "wrong buffer type\n"); + retval = -EINVAL; + break; + } + + mxc_streamoff(cam); + mxc_free_frame_buf(cam); + cam->enc_counter = 0; + cam->skip_frame = 0; + INIT_LIST_HEAD(&cam->ready_q); + INIT_LIST_HEAD(&cam->working_q); + INIT_LIST_HEAD(&cam->done_q); + + retval = mxc_allocate_frame_buf(cam, req->count); + break; + } + + /*! + * V4l2 VIDIOC_QUERYBUF ioctl + */ + case VIDIOC_QUERYBUF: { + struct v4l2_buffer *buf = arg; + int index = buf->index; + pr_debug(" case VIDIOC_QUERYBUF\n"); + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_err("ERROR: v4l2 capture: " + "VIDIOC_QUERYBUFS: " + "wrong buffer type\n"); + retval = -EINVAL; + break; + } + + memset(buf, 0, sizeof(buf)); + buf->index = index; + + down(&cam->param_lock); + retval = mxc_v4l2_buffer_status(cam, buf); + up(&cam->param_lock); + break; + } + + /*! + * V4l2 VIDIOC_QBUF ioctl + */ + case VIDIOC_QBUF: { + struct v4l2_buffer *buf = arg; + int index = buf->index; + pr_debug(" case VIDIOC_QBUF\n"); + + spin_lock_irqsave(&cam->queue_int_lock, lock_flags); + cam->frame[index].buffer.m.offset = buf->m.offset; + if ((cam->frame[index].buffer.flags & 0x7) == + V4L2_BUF_FLAG_MAPPED) { + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI IC MEM") == 0) { + if (cam->skip_frame > 0) { + list_add_tail(&cam->frame[index].queue, + &cam->working_q); + + retval = + cam->enc_update_eba(cam-> + frame[index]. + buffer.m.offset, + &cam-> + ping_pong_csi); + + cam->skip_frame = 0; + } else + list_add_tail(&cam->frame[index].queue, + &cam->ready_q); + } else if (strcmp( + mxc_capture_inputs[cam->current_input]. + name, "CSI MEM") == 0) { + list_add_tail(&cam->frame[index].queue, + &cam->ready_q); + } + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_QUEUED) { + pr_err("ERROR: v4l2 capture: VIDIOC_QBUF: " + "buffer already queued\n"); + retval = -EINVAL; + } else if (cam->frame[index].buffer. + flags & V4L2_BUF_FLAG_DONE) { + pr_err("ERROR: v4l2 capture: VIDIOC_QBUF: " + "overwrite done buffer.\n"); + cam->frame[index].buffer.flags &= + ~V4L2_BUF_FLAG_DONE; + cam->frame[index].buffer.flags |= + V4L2_BUF_FLAG_QUEUED; + retval = -EINVAL; + } + + buf->flags = cam->frame[index].buffer.flags; + spin_unlock_irqrestore(&cam->queue_int_lock, lock_flags); + break; + } + + /*! + * V4l2 VIDIOC_DQBUF ioctl + */ + case VIDIOC_DQBUF: { + struct v4l2_buffer *buf = arg; + pr_debug(" case VIDIOC_DQBUF\n"); + + if ((cam->enc_counter == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + retval = mxc_v4l_dqueue(cam, buf); + + break; + } + + /*! + * V4l2 VIDIOC_STREAMON ioctl + */ + case VIDIOC_STREAMON: { + pr_debug(" case VIDIOC_STREAMON\n"); + retval = mxc_streamon(cam); + break; + } + + /*! + * V4l2 VIDIOC_STREAMOFF ioctl + */ + case VIDIOC_STREAMOFF: { + pr_debug(" case VIDIOC_STREAMOFF\n"); + retval = mxc_streamoff(cam); + break; + } + + /*! + * V4l2 VIDIOC_G_CTRL ioctl + */ + case VIDIOC_G_CTRL: { + pr_debug(" case VIDIOC_G_CTRL\n"); + retval = mxc_v4l2_g_ctrl(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_S_CTRL ioctl + */ + case VIDIOC_S_CTRL: { + pr_debug(" case VIDIOC_S_CTRL\n"); + retval = mxc_v4l2_s_ctrl(cam, arg); + break; + } + + /*! + * V4l2 VIDIOC_CROPCAP ioctl + */ + case VIDIOC_CROPCAP: { + struct v4l2_cropcap *cap = arg; + pr_debug(" case VIDIOC_CROPCAP\n"); + if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + cap->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + cap->bounds = cam->crop_bounds; + cap->defrect = cam->crop_defrect; + break; + } + + /*! + * V4l2 VIDIOC_G_CROP ioctl + */ + case VIDIOC_G_CROP: { + struct v4l2_crop *crop = arg; + pr_debug(" case VIDIOC_G_CROP\n"); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + crop->c = cam->crop_current; + break; + } + + /*! + * V4l2 VIDIOC_S_CROP ioctl + */ + case VIDIOC_S_CROP: { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = &cam->crop_bounds; + pr_debug(" case VIDIOC_S_CROP\n"); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + crop->type != V4L2_BUF_TYPE_VIDEO_OVERLAY) { + retval = -EINVAL; + break; + } + + crop->c.top = (crop->c.top < b->top) ? b->top + : crop->c.top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top + b->height - crop->c.top) + crop->c.height = + b->top + b->height - crop->c.top; + + crop->c.left = (crop->c.left < b->left) ? b->left + : crop->c.left; + if (crop->c.left > b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + crop->c.width -= crop->c.width % 8; + crop->c.left -= crop->c.left % 4; + cam->crop_current = crop->c; + + pr_debug(" Cropping Input to ipu size %d x %d\n", + cam->crop_current.width, + cam->crop_current.height); + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.height, + cam->csi); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top, + cam->csi); + break; + } + + /*! + * V4l2 VIDIOC_OVERLAY ioctl + */ + case VIDIOC_OVERLAY: { + int *on = arg; + pr_debug(" VIDIOC_OVERLAY on=%d\n", *on); + if (*on) { + cam->overlay_on = true; + cam->overlay_pid = current->pid; + retval = start_preview(cam); + } + if (!*on) { + retval = stop_preview(cam); + cam->overlay_on = false; + } + break; + } + + /*! + * V4l2 VIDIOC_G_FBUF ioctl + */ + case VIDIOC_G_FBUF: { + struct v4l2_framebuffer *fb = arg; + pr_debug(" case VIDIOC_G_FBUF\n"); + *fb = cam->v4l2_fb; + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + break; + } + + /*! + * V4l2 VIDIOC_S_FBUF ioctl + */ + case VIDIOC_S_FBUF: { + struct v4l2_framebuffer *fb = arg; + pr_debug(" case VIDIOC_S_FBUF\n"); + cam->v4l2_fb = *fb; + break; + } + + case VIDIOC_G_PARM: { + struct v4l2_streamparm *parm = arg; + pr_debug(" case VIDIOC_G_PARM\n"); + vidioc_int_g_parm(cam->sensor, parm); + break; + } + + case VIDIOC_S_PARM: { + struct v4l2_streamparm *parm = arg; + pr_debug(" case VIDIOC_S_PARM\n"); + retval = mxc_v4l2_s_param(cam, parm); + break; + } + + /* linux v4l2 bug, kernel c0485619 user c0405619 */ + case VIDIOC_ENUMSTD: { + struct v4l2_standard *e = arg; + pr_debug(" case VIDIOC_ENUMSTD\n"); + *e = cam->standard; + break; + } + + case VIDIOC_G_STD: { + v4l2_std_id *e = arg; + pr_debug(" case VIDIOC_G_STD\n"); + retval = mxc_v4l2_g_std(cam, e); + break; + } + + case VIDIOC_S_STD: { + v4l2_std_id *e = arg; + pr_debug(" case VIDIOC_S_STD\n"); + retval = mxc_v4l2_s_std(cam, *e); + + break; + } + + case VIDIOC_ENUMOUTPUT: { + struct v4l2_output *output = arg; + pr_debug(" case VIDIOC_ENUMOUTPUT\n"); + if (output->index >= MXC_V4L2_CAPTURE_NUM_OUTPUTS) { + retval = -EINVAL; + break; + } + *output = mxc_capture_outputs[output->index]; + + break; + } + case VIDIOC_G_OUTPUT: { + int *p_output_num = arg; + pr_debug(" case VIDIOC_G_OUTPUT\n"); + *p_output_num = cam->output; + break; + } + + case VIDIOC_S_OUTPUT: { + int *p_output_num = arg; + pr_debug(" case VIDIOC_S_OUTPUT\n"); + if (*p_output_num >= MXC_V4L2_CAPTURE_NUM_OUTPUTS) { + retval = -EINVAL; + break; + } + cam->output = *p_output_num; + break; + } + + case VIDIOC_ENUMINPUT: { + struct v4l2_input *input = arg; + pr_debug(" case VIDIOC_ENUMINPUT\n"); + if (input->index >= MXC_V4L2_CAPTURE_NUM_INPUTS) { + retval = -EINVAL; + break; + } + *input = mxc_capture_inputs[input->index]; + break; + } + + case VIDIOC_G_INPUT: { + int *index = arg; + pr_debug(" case VIDIOC_G_INPUT\n"); + *index = cam->current_input; + break; + } + + case VIDIOC_S_INPUT: { + int *index = arg; + pr_debug(" case VIDIOC_S_INPUT\n"); + if (*index >= MXC_V4L2_CAPTURE_NUM_INPUTS) { + retval = -EINVAL; + break; + } + + if (*index == cam->current_input) + break; + + if ((mxc_capture_inputs[cam->current_input].status & + V4L2_IN_ST_NO_POWER) == 0) { + retval = mxc_streamoff(cam); + if (retval) + break; + mxc_capture_inputs[cam->current_input].status |= + V4L2_IN_ST_NO_POWER; + } + + if (strcmp(mxc_capture_inputs[*index].name, "CSI MEM") == 0) { +#if defined(CONFIG_MXC_IPU_CSI_ENC) || defined(CONFIG_MXC_IPU_CSI_ENC_MODULE) + retval = csi_enc_select(cam); + if (retval) + break; +#endif + } else if (strcmp(mxc_capture_inputs[*index].name, + "CSI IC MEM") == 0) { +#if defined(CONFIG_MXC_IPU_PRP_ENC) || defined(CONFIG_MXC_IPU_PRP_ENC_MODULE) + retval = prp_enc_select(cam); + if (retval) + break; +#endif + } + + mxc_capture_inputs[*index].status &= ~V4L2_IN_ST_NO_POWER; + cam->current_input = *index; + break; + } + + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + pr_debug(" case default or not supported\n"); + retval = -EINVAL; + break; + } + + up(&cam->busy_lock); + return retval; +} + +/* + * V4L interface - ioctl function + * + * @return None + */ +static long mxc_v4l_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + pr_debug("In MVC:mxc_v4l_ioctl\n"); + return video_usercopy(file, cmd, arg, mxc_v4l_do_ioctl); +} + +/*! + * V4L interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = video_devdata(file); + unsigned long size; + int res = 0; + cam_data *cam = video_get_drvdata(dev); + + pr_debug("In MVC:mxc_mmap\n"); + pr_debug(" pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_err("ERROR: v4l2 capture: mxc_mmap: " + "remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mxc_mmap_exit: + up(&cam->busy_lock); + return res; +} + +/*! + * V4L interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table_struct * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_poll(struct file *file, struct poll_table_struct *wait) +{ + struct video_device *dev = video_devdata(file); + cam_data *cam = video_get_drvdata(dev); + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + pr_debug("In MVC:mxc_poll\n"); + + if (down_interruptible(&cam->busy_lock)) + return -EINTR; + + queue = &cam->enc_queue; + poll_wait(file, queue, wait); + + up(&cam->busy_lock); + + return res; +} + +/*! + * This structure defines the functions to be called in this driver. + */ +static struct v4l2_file_operations mxc_v4l_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l_open, + .release = mxc_v4l_close, + .read = mxc_v4l_read, + .ioctl = mxc_v4l_ioctl, + .mmap = mxc_mmap, + .poll = mxc_poll, +}; + +static struct video_device mxc_v4l_template = { + .name = "Mxc Camera", + .fops = &mxc_v4l_fops, + .release = video_device_release, +}; + +/*! + * This function can be used to release any platform data on closing. + */ +static void camera_platform_release(struct device *device) +{ +} + +/*! + * Camera V4l2 callback function. + * + * @param mask u32 + * + * @param dev void device structure + * + * @return status + */ +static void camera_callback(u32 mask, void *dev) +{ + struct mxc_v4l_frame *done_frame; + struct mxc_v4l_frame *ready_frame; + struct timeval cur_time; + + cam_data *cam = (cam_data *) dev; + if (cam == NULL) + return; + + pr_debug("In MVC:camera_callback\n"); + + if (strcmp(mxc_capture_inputs[cam->current_input].name, "CSI IC MEM") + == 0) { + if (list_empty(&cam->working_q)) { + pr_err("ERROR: v4l2 capture: camera_callback: " + "working queue empty\n"); + return; + } + do_gettimeofday(&cur_time); + + done_frame = list_entry(cam->working_q.next, + struct mxc_v4l_frame, + queue); + + /* + * Set the current time to done frame buffer's timestamp. + * Users can use this information to judge the frame's usage. + */ + done_frame->buffer.timestamp = cur_time; + if (done_frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + done_frame->buffer.flags |= V4L2_BUF_FLAG_DONE; + done_frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; + + /* Added to the done queue */ + list_del(cam->working_q.next); + list_add_tail(&done_frame->queue, &cam->done_q); + + /* Wake up the queue */ + cam->enc_counter++; + wake_up_interruptible(&cam->enc_queue); + + if (list_empty(&cam->ready_q)) { + cam->skip_frame++; + } else { + ready_frame = list_entry(cam->ready_q.next, + struct mxc_v4l_frame, + queue); + + if (cam->enc_update_eba( + ready_frame->buffer.m.offset, + &cam->ping_pong_csi) == 0) { + list_del(cam->ready_q.next); + list_add_tail(&ready_frame->queue, + &cam->working_q); + } else + return; + } + } else { + pr_err("ERROR: v4l2 capture: camera_callback: " + "buffer not queued\n"); + } + } else if (strcmp(mxc_capture_inputs[cam->current_input].name, + "CSI MEM") == 0) { + if (!list_empty(&cam->working_q)) { + do_gettimeofday(&cur_time); + + done_frame = list_entry(cam->working_q.next, + struct mxc_v4l_frame, + queue); + + /* + * Set the current time to done frame buffer's + * timestamp. Users can use this information to judge + * the frame's usage. + */ + done_frame->buffer.timestamp = cur_time; + + if (done_frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { + done_frame->buffer.flags |= + V4L2_BUF_FLAG_DONE; + done_frame->buffer.flags &= + ~V4L2_BUF_FLAG_QUEUED; + + /* Added to the done queue */ + list_del(cam->working_q.next); + list_add_tail(&done_frame->queue, &cam->done_q); + + /* Wake up the queue */ + cam->enc_counter++; + wake_up_interruptible(&cam->enc_queue); + } else { + pr_err("ERROR: v4l2 capture: camera_callback: " + "buffer not queued\n"); + } + } + + if (!list_empty(&cam->ready_q)) { + ready_frame = list_entry(cam->ready_q.next, + struct mxc_v4l_frame, + queue); + if (cam->enc_update_eba(ready_frame->buffer.m.offset, + &cam->ping_pong_csi) == 0) { + list_del(cam->ready_q.next); + list_add_tail(&ready_frame->queue, + &cam->working_q); + } else + return; + } else { + if (cam->enc_update_eba( + cam->dummy_frame.buffer.m.offset, + &cam->ping_pong_csi) == -EACCES) + return; + } + } + + return; +} + +/*! + * initialize cam_data structure + * + * @param cam structure cam_data * + * + * @return status 0 Success + */ +static void init_camera_struct(cam_data *cam, struct platform_device *pdev) +{ + pr_debug("In MVC: init_camera_struct\n"); + + /* Default everything to 0 */ + memset(cam, 0, sizeof(cam_data)); + + init_MUTEX(&cam->param_lock); + init_MUTEX(&cam->busy_lock); + + cam->video_dev = video_device_alloc(); + if (cam->video_dev == NULL) + return; + + *(cam->video_dev) = mxc_v4l_template; + + video_set_drvdata(cam->video_dev, cam); + dev_set_drvdata(&pdev->dev, (void *)cam); + cam->video_dev->minor = -1; + + init_waitqueue_head(&cam->enc_queue); + init_waitqueue_head(&cam->still_queue); + + /* setup cropping */ + cam->crop_bounds.left = 0; + cam->crop_bounds.width = 640; + cam->crop_bounds.top = 0; + cam->crop_bounds.height = 480; + cam->crop_current = cam->crop_defrect = cam->crop_bounds; + ipu_csi_set_window_size(cam->crop_current.width, + cam->crop_current.height, cam->csi); + ipu_csi_set_window_pos(cam->crop_current.left, + cam->crop_current.top, cam->csi); + cam->streamparm.parm.capture.capturemode = 0; + + cam->standard.index = 0; + cam->standard.id = V4L2_STD_UNKNOWN; + cam->standard.frameperiod.denominator = 30; + cam->standard.frameperiod.numerator = 1; + cam->standard.framelines = 480; + cam->standard_autodetect = true; + cam->streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cam->streamparm.parm.capture.timeperframe = cam->standard.frameperiod; + cam->streamparm.parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + cam->overlay_on = false; + cam->capture_on = false; + cam->skip_frame = 0; + cam->v4l2_fb.flags = V4L2_FBUF_FLAG_OVERLAY; + + cam->v2f.fmt.pix.sizeimage = 352 * 288 * 3 / 2; + cam->v2f.fmt.pix.bytesperline = 288 * 3 / 2; + cam->v2f.fmt.pix.width = 288; + cam->v2f.fmt.pix.height = 352; + cam->v2f.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + cam->win.w.width = 160; + cam->win.w.height = 160; + cam->win.w.left = 0; + cam->win.w.top = 0; + + cam->csi = 0; /* Need to determine how to set this correctly with + * multiple video input devices. */ + + cam->enc_callback = camera_callback; + init_waitqueue_head(&cam->power_queue); + spin_lock_init(&cam->queue_int_lock); + spin_lock_init(&cam->dqueue_int_lock); +} + +/*! + * camera_power function + * Turns Sensor power On/Off + * + * @param cam cam data struct + * @param cameraOn true to turn camera on, false to turn off power. + * + * @return status + */ +static u8 camera_power(cam_data *cam, bool cameraOn) +{ + pr_debug("In MVC:camera_power on=%d\n", cameraOn); + + if (cameraOn == true) { + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + vidioc_int_s_power(cam->sensor, 1); + } else { + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + vidioc_int_s_power(cam->sensor, 0); + } + return 0; +} + +/*! + * This function is called to probe the devices if registered. + * + * @param pdev the device structure used to give information on which device + * to probe + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxc_v4l2_probe(struct platform_device *pdev) +{ + /* Create g_cam and initialize it. */ + g_cam = kmalloc(sizeof(cam_data), GFP_KERNEL); + if (g_cam == NULL) { + pr_err("ERROR: v4l2 capture: failed to register camera\n"); + return -1; + } + init_camera_struct(g_cam, pdev); + pdev->dev.release = camera_platform_release; + + /* Set up the v4l2 device and register it*/ + mxc_v4l2_int_device.priv = g_cam; + /* This function contains a bug that won't let this be rmmod'd. */ + v4l2_int_device_register(&mxc_v4l2_int_device); + + /* register v4l video device */ + if (video_register_device(g_cam->video_dev, VFL_TYPE_GRABBER, video_nr) + == -1) { + kfree(g_cam); + g_cam = NULL; + pr_err("ERROR: v4l2 capture: video_register_device failed\n"); + return -1; + } + pr_debug(" Video device registered: %s #%d\n", + g_cam->video_dev->name, g_cam->video_dev->minor); + + return 0; +} + +/*! + * This function is called to remove the devices when device unregistered. + * + * @param pdev the device structure used to give information on which device + * to remove + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxc_v4l2_remove(struct platform_device *pdev) +{ + + if (g_cam->open_count) { + pr_err("ERROR: v4l2 capture:camera open " + "-- setting ops to NULL\n"); + return -EBUSY; + } else { + pr_info("V4L2 freeing image input device\n"); + v4l2_int_device_unregister(&mxc_v4l2_int_device); + video_unregister_device(g_cam->video_dev); + + mxc_free_frame_buf(g_cam); + kfree(g_cam); + g_cam = NULL; + } + + pr_info("V4L2 unregistering video\n"); + return 0; +} + +/*! + * This function is called to put the sensor in a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure used to give information on which I2C + * to suspend + * @param state the power state the device is entering + * + * @return The function returns 0 on success and -1 on failure. + */ +static int mxc_v4l2_suspend(struct platform_device *pdev, pm_message_t state) +{ + cam_data *cam = platform_get_drvdata(pdev); + + pr_debug("In MVC:mxc_v4l2_suspend\n"); + + if (cam == NULL) { + return -1; + } + + cam->low_power = true; + + if (cam->overlay_on == true) + stop_preview(cam); + if ((cam->capture_on == true) && cam->enc_disable) { + cam->enc_disable(cam); + } + camera_power(cam, false); + + return 0; +} + +/*! + * This function is called to bring the sensor back from a low power state. + * Refer to the document driver-model/driver.txt in the kernel source tree + * for more information. + * + * @param pdev the device structure + * + * @return The function returns 0 on success and -1 on failure + */ +static int mxc_v4l2_resume(struct platform_device *pdev) +{ + cam_data *cam = platform_get_drvdata(pdev); + + pr_debug("In MVC:mxc_v4l2_resume\n"); + + if (cam == NULL) { + return -1; + } + + cam->low_power = false; + wake_up_interruptible(&cam->power_queue); + camera_power(cam, true); + + if (cam->overlay_on == true) + start_preview(cam); + if (cam->capture_on == true) + mxc_streamon(cam); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2_driver = { + .driver = { + .name = "mxc_v4l2_capture", + }, + .probe = mxc_v4l2_probe, + .remove = mxc_v4l2_remove, + .suspend = mxc_v4l2_suspend, + .resume = mxc_v4l2_resume, + .shutdown = NULL, +}; + +/*! + * Initializes the camera driver. + */ +static int mxc_v4l2_master_attach(struct v4l2_int_device *slave) +{ + cam_data *cam = slave->u.slave->master->priv; + struct v4l2_format cam_fmt; + + pr_debug("In MVC: mxc_v4l2_master_attach\n"); + pr_debug(" slave.name = %s\n", slave->name); + pr_debug(" master.name = %s\n", slave->u.slave->master->name); + + cam->sensor = slave; + if (slave == NULL) { + pr_err("ERROR: v4l2 capture: slave parameter not valid.\n"); + return -1; + } + + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true); + vidioc_int_s_power(cam->sensor, 1); + vidioc_int_dev_init(slave); + ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false); + cam_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vidioc_int_g_fmt_cap(cam->sensor, &cam_fmt); + + /* Used to detect TV in (type 1) vs. camera (type 0)*/ + cam->device_type = cam_fmt.fmt.pix.priv; + + /* Set the input size to the ipu for this device */ + cam->crop_bounds.top = cam->crop_bounds.left = 0; + cam->crop_bounds.width = cam_fmt.fmt.pix.width; + cam->crop_bounds.height = cam_fmt.fmt.pix.height; + + /* This also is the max crop size for this device. */ + cam->crop_defrect.top = cam->crop_defrect.left = 0; + cam->crop_defrect.width = cam_fmt.fmt.pix.width; + cam->crop_defrect.height = cam_fmt.fmt.pix.height; + + /* At this point, this is also the current image size. */ + cam->crop_current.top = cam->crop_current.left = 0; + cam->crop_current.width = cam_fmt.fmt.pix.width; + cam->crop_current.height = cam_fmt.fmt.pix.height; + + pr_debug("End of %s: v2f pix widthxheight %d x %d\n", + __func__, + cam->v2f.fmt.pix.width, cam->v2f.fmt.pix.height); + pr_debug("End of %s: crop_bounds widthxheight %d x %d\n", + __func__, + cam->crop_bounds.width, cam->crop_bounds.height); + pr_debug("End of %s: crop_defrect widthxheight %d x %d\n", + __func__, + cam->crop_defrect.width, cam->crop_defrect.height); + pr_debug("End of %s: crop_current widthxheight %d x %d\n", + __func__, + cam->crop_current.width, cam->crop_current.height); + + return 0; +} + +/*! + * Disconnects the camera driver. + */ +static void mxc_v4l2_master_detach(struct v4l2_int_device *slave) +{ + pr_debug("In MVC:mxc_v4l2_master_detach\n"); + vidioc_int_dev_exit(slave); +} + +/*! + * Entry point for the V4L2 + * + * @return Error code indicating success or failure + */ +static __init int camera_init(void) +{ + u8 err = 0; + + pr_debug("In MVC:camera_init\n"); + + /* Register the device driver structure. */ + err = platform_driver_register(&mxc_v4l2_driver); + if (err != 0) { + pr_err("ERROR: v4l2 capture:camera_init: " + "platform_driver_register failed.\n"); + return err; + } + + return err; +} + +/*! + * Exit and cleanup for the V4L2 + */ +static void __exit camera_exit(void) +{ + pr_debug("In MVC: camera_exit\n"); + + platform_driver_unregister(&mxc_v4l2_driver); +} + +module_init(camera_init); +module_exit(camera_exit); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2 capture driver for Mxc based cameras"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/capture/mxc_v4l2_capture.h b/drivers/media/video/mxc/capture/mxc_v4l2_capture.h new file mode 100644 index 000000000000..50f695102095 --- /dev/null +++ b/drivers/media/video/mxc/capture/mxc_v4l2_capture.h @@ -0,0 +1,206 @@ +/* + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup MXC_V4L2_CAPTURE MXC V4L2 Video Capture Driver + */ +/*! + * @file mxc_v4l2_capture.h + * + * @brief mxc V4L2 capture device API Header file + * + * It include all the defines for frame operations, also three structure defines + * use case ops structure, common v4l2 driver structure and frame structure. + * + * @ingroup MXC_V4L2_CAPTURE + */ +#ifndef __MXC_V4L2_CAPTURE_H__ +#define __MXC_V4L2_CAPTURE_H__ + +#include <asm/uaccess.h> +#include <linux/list.h> +#include <linux/smp_lock.h> +#include <linux/ipu.h> +#include <linux/mxc_v4l2.h> + +#include <media/v4l2-dev.h> + +#define FRAME_NUM 3 + +/*! + * v4l2 frame structure. + */ +struct mxc_v4l_frame { + u32 paddress; + void *vaddress; + int count; + int width; + int height; + + struct v4l2_buffer buffer; + struct list_head queue; + int index; +}; + +/* Only for old version. Will go away soon. */ +typedef struct { + u8 clk_mode; + u8 ext_vsync; + u8 Vsync_pol; + u8 Hsync_pol; + u8 pixclk_pol; + u8 data_pol; + u8 data_width; + u8 pack_tight; + u8 force_eof; + u8 data_en_pol; + u16 width; + u16 height; + u32 pixel_fmt; + u32 mclk; + u16 active_width; + u16 active_height; +} sensor_interface; + +/* Sensor control function */ +/* Only for old version. Will go away soon. */ +struct camera_sensor { + void (*set_color) (int bright, int saturation, int red, int green, + int blue); + void (*get_color) (int *bright, int *saturation, int *red, int *green, + int *blue); + void (*set_ae_mode) (int ae_mode); + void (*get_ae_mode) (int *ae_mode); + sensor_interface *(*config) (int *frame_rate, int high_quality); + sensor_interface *(*reset) (void); + void (*get_std) (v4l2_std_id *std); + void (*set_std) (v4l2_std_id std); + unsigned int csi; +}; + +/*! + * common v4l2 driver structure. + */ +typedef struct _cam_data { + struct video_device *video_dev; + int device_type; + + /* semaphore guard against SMP multithreading */ + struct semaphore busy_lock; + + int open_count; + + /* params lock for this camera */ + struct semaphore param_lock; + + /* Encoder */ + struct list_head ready_q; + struct list_head done_q; + struct list_head working_q; + int ping_pong_csi; + spinlock_t queue_int_lock; + spinlock_t dqueue_int_lock; + struct mxc_v4l_frame frame[FRAME_NUM]; + struct mxc_v4l_frame dummy_frame; + int skip_frame; + wait_queue_head_t enc_queue; + int enc_counter; + dma_addr_t rot_enc_bufs[2]; + void *rot_enc_bufs_vaddr[2]; + int rot_enc_buf_size[2]; + enum v4l2_buf_type type; + + /* still image capture */ + wait_queue_head_t still_queue; + int still_counter; + dma_addr_t still_buf[2]; + void *still_buf_vaddr; + + /* overlay */ + struct v4l2_window win; + struct v4l2_framebuffer v4l2_fb; + dma_addr_t vf_bufs[2]; + void *vf_bufs_vaddr[2]; + int vf_bufs_size[2]; + dma_addr_t rot_vf_bufs[2]; + void *rot_vf_bufs_vaddr[2]; + int rot_vf_buf_size[2]; + bool overlay_active; + int output; + struct fb_info *overlay_fb; + int fb_origin_std; + + /* v4l2 format */ + struct v4l2_format v2f; + int rotation; /* for IPUv1 and IPUv3, this means encoder rotation */ + int vf_rotation; /* viewfinder rotation only for IPUv1 and IPUv3 */ + struct v4l2_mxc_offset offset; + + /* V4l2 control bit */ + int bright; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + /* standard */ + struct v4l2_streamparm streamparm; + struct v4l2_standard standard; + bool standard_autodetect; + + /* crop */ + struct v4l2_rect crop_bounds; + struct v4l2_rect crop_defrect; + struct v4l2_rect crop_current; + + int (*enc_update_eba) (dma_addr_t eba, int *bufferNum); + int (*enc_enable) (void *private); + int (*enc_disable) (void *private); + int (*enc_enable_csi) (void *private); + int (*enc_disable_csi) (void *private); + void (*enc_callback) (u32 mask, void *dev); + int (*vf_start_adc) (void *private); + int (*vf_stop_adc) (void *private); + int (*vf_start_sdc) (void *private); + int (*vf_stop_sdc) (void *private); + int (*vf_enable_csi) (void *private); + int (*vf_disable_csi) (void *private); + int (*csi_start) (void *private); + int (*csi_stop) (void *private); + + /* misc status flag */ + bool overlay_on; + bool capture_on; + int overlay_pid; + int capture_pid; + bool low_power; + wait_queue_head_t power_queue; + unsigned int csi; + int current_input; + + /* camera sensor interface */ + struct camera_sensor *cam_sensor; /* old version */ + struct v4l2_int_device *sensor; +} cam_data; + +#if defined(CONFIG_MXC_IPU_V1) || defined(CONFIG_VIDEO_MXC_EMMA_CAMERA) \ + || defined(CONFIG_VIDEO_MXC_CSI_CAMERA_MODULE) \ + || defined(CONFIG_VIDEO_MXC_CSI_CAMERA) +void set_mclk_rate(uint32_t *p_mclk_freq); +#else +void set_mclk_rate(uint32_t *p_mclk_freq, uint32_t csi); +#endif +#endif /* __MXC_V4L2_CAPTURE_H__ */ diff --git a/drivers/media/video/mxc/capture/ov2640.c b/drivers/media/video/mxc/capture/ov2640.c new file mode 100644 index 000000000000..c906925eca21 --- /dev/null +++ b/drivers/media/video/mxc/capture/ov2640.c @@ -0,0 +1,1081 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ov2640.c + * + * @brief ov2640 camera driver functions + * + * @ingroup Camera + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> + +#include <mach/hardware.h> +#include <media/v4l2-int-device.h> +#include "mxc_v4l2_capture.h" + +#define MIN_FPS 5 +#define MAX_FPS 30 +#define DEFAULT_FPS 30 + +#define OV2640_XCLK_MIN 6000000 +#define OV2640_XCLK_MAX 27000000 + +/* +enum ov2640_mode { + ov2640_mode_1600_1120, + ov2640_mode_800_600 +}; +*/ + +struct reg_value { + u8 reg; + u8 value; + int delay_ms; +}; + +static struct reg_value ov2640_setting_1600_1120[] = { +#ifdef CONFIG_MACH_MX25_3DS + {0xff, 0x01, 0}, {0x12, 0x80, 5}, {0xff, 0x00, 0}, {0x2c, 0xff, 0}, + {0x2e, 0xdf, 0}, {0xff, 0x01, 0}, {0x3c, 0x32, 0}, {0x11, 0x00, 0}, + {0x09, 0x02, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, {0x14, 0x48, 0}, + {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, {0x3b, 0xfb, 0}, + {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, {0x39, 0x02, 0}, + {0x35, 0x58, 0}, {0x22, 0x0a, 0}, {0x37, 0x40, 0}, {0x23, 0x00, 0}, + {0x34, 0xa0, 0}, {0x36, 0x1a, 0}, {0x06, 0x02, 0}, {0x07, 0xc0, 0}, + {0x0d, 0xb7, 0}, {0x0e, 0x01, 0}, {0x4c, 0x00, 0}, {0x4a, 0x81, 0}, + {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, {0x26, 0x82, 0}, + {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x3f, 0}, {0x61, 0x70, 0}, + {0x62, 0x80, 0}, {0x7c, 0x05, 0}, {0x20, 0x80, 0}, {0x28, 0x30, 0}, + {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, {0x6e, 0x00, 0}, {0x70, 0x02, 0}, + {0x71, 0x94, 0}, {0x73, 0xc1, 0}, {0x3d, 0x34, 0}, {0x5a, 0x57, 0}, + {0x4f, 0xbb, 0}, {0x50, 0x9c, 0}, {0xff, 0x00, 0}, {0xe5, 0x7f, 0}, + {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0xe0, 0x14, 0}, {0x76, 0xff, 0}, + {0x33, 0xa0, 0}, {0x42, 0x20, 0}, {0x43, 0x18, 0}, {0x4c, 0x00, 0}, + {0x87, 0xd0, 0}, {0x88, 0x3f, 0}, {0xd7, 0x01, 0}, {0xd9, 0x10, 0}, + {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, {0xc9, 0x80, 0}, {0x7c, 0x00, 0}, + {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, {0x7d, 0x48, 0}, {0x7d, 0x48, 0}, + {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, {0x7d, 0x10, 0}, {0x7d, 0x0e, 0}, + {0x90, 0x00, 0}, {0x91, 0x0e, 0}, {0x91, 0x1a, 0}, {0x91, 0x31, 0}, + {0x91, 0x5a, 0}, {0x91, 0x69, 0}, {0x91, 0x75, 0}, {0x91, 0x7e, 0}, + {0x91, 0x88, 0}, {0x91, 0x8f, 0}, {0x91, 0x96, 0}, {0x91, 0xa3, 0}, + {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, {0x91, 0xd7, 0}, {0x91, 0xe8, 0}, + {0x91, 0x20, 0}, {0x92, 0x00, 0}, {0x93, 0x06, 0}, {0x93, 0xe3, 0}, + {0x93, 0x05, 0}, {0x93, 0x05, 0}, {0x93, 0x00, 0}, {0x93, 0x04, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x96, 0x00, 0}, + {0x97, 0x08, 0}, {0x97, 0x19, 0}, {0x97, 0x02, 0}, {0x97, 0x0c, 0}, + {0x97, 0x24, 0}, {0x97, 0x30, 0}, {0x97, 0x28, 0}, {0x97, 0x26, 0}, + {0x97, 0x02, 0}, {0x97, 0x98, 0}, {0x97, 0x80, 0}, {0x97, 0x00, 0}, + {0x97, 0x00, 0}, {0xc3, 0xed, 0}, {0xa4, 0x00, 0}, {0xa8, 0x00, 0}, + {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, {0xc7, 0x10, 0}, + {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, {0xb9, 0x7c, 0}, + {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, {0xb0, 0xc5, 0}, + {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, {0xc0, 0xc8, 0}, + {0xc1, 0x96, 0}, {0x86, 0x1d, 0}, {0x50, 0x00, 0}, {0x51, 0x90, 0}, + {0x52, 0x2c, 0}, {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x88, 0}, + {0x57, 0x00, 0}, {0x5a, 0x90, 0}, {0x5b, 0x2c, 0}, {0x5c, 0x05, 0}, + {0xc3, 0xed, 0}, {0x7f, 0x00, 0}, {0xda, 0x00, 0}, {0xe5, 0x1f, 0}, + {0xe1, 0x77, 0}, {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0}, + {0xff, 0x00, 0}, {0xe0, 0x04, 0}, {0xc0, 0xc8, 0}, {0xc1, 0x96, 0}, + {0x86, 0x3d, 0}, {0x50, 0x00, 0}, {0x51, 0x90, 0}, {0x52, 0x2c, 0}, + {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x88, 0}, {0x57, 0x00, 0}, + {0x5a, 0x40, 0}, {0x5b, 0xf0, 0}, {0x5c, 0x01, 0}, {0xd3, 0x82, 0}, + {0xe0, 0x00, 1000} +#else + {0xff, 0x1, 0}, {0x12, 0x80, 1}, {0xff, 0, 0}, {0x2c, 0xff, 0}, + {0x2e, 0xdf, 0}, {0xff, 0x1, 0}, {0x3c, 0x32, 0}, {0x11, 0x01, 0}, + {0x09, 0x00, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, {0x14, 0x48, 0}, + {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, {0x3b, 0xfb, 0}, + {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, {0x39, 0x82, 0}, + {0x35, 0x88, 0}, {0x22, 0x0a, 0}, {0x37, 0x40, 0}, {0x23, 0x00, 0}, + {0x34, 0xa0, 0}, {0x36, 0x1a, 0}, {0x06, 0x02, 0}, {0x07, 0xc0, 0}, + {0x0d, 0xb7, 0}, {0x0e, 0x01, 0}, {0x4c, 0x00, 0}, {0x4a, 0x81, 0}, + {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, {0x26, 0x82, 0}, + {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x3f, 0}, {0x0c, 0x3c, 0}, + {0x5d, 0x55, 0}, {0x5e, 0x7d, 0}, {0x5f, 0x7d, 0}, {0x60, 0x55, 0}, + {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, {0x20, 0x80, 0}, + {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, {0x6e, 00, 0}, + {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, {0x3d, 0x34, 0}, + {0x5a, 0x57, 0}, {0x4f, 0xbb, 0}, {0x50, 0x9c, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0x44, 0x06, 0}, + {0xe0, 0x14, 0}, {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, + {0x43, 0x18, 0}, {0x4c, 0x00, 0}, {0x87, 0xd0, 0}, {0xd7, 0x03, 0}, + {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, {0xc9, 0x80, 0}, + {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, {0x7d, 0x48, 0}, + {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, {0x7d, 0x10, 0}, + {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, {0x91, 0x1a, 0}, + {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, {0x91, 0x75, 0}, + {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, {0x91, 0x96, 0}, + {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, {0x91, 0xd7, 0}, + {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, {0x93, 0x06, 0}, + {0x93, 0xe3, 0}, {0x93, 0x03, 0}, {0x93, 0x03, 0}, {0x93, 0x00, 0}, + {0x93, 0x02, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, {0x97, 0x02, 0}, + {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, {0x97, 0x28, 0}, + {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, {0x97, 0x80, 0}, + {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xa4, 0x00, 0}, {0xa8, 0x00, 0}, + {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, {0xc7, 0x10, 0}, + {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, {0xb9, 0x7c, 0}, + {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, {0xb0, 0xc5, 0}, + {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, {0xa6, 0x00, 0}, + {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x1b, 0}, {0xa7, 0x31, 0}, + {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, + {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, + {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, + {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xc0, 0xc8, 0}, {0xc1, 0x96, 0}, + {0x86, 0x3d, 0}, {0x50, 0x00, 0}, {0x51, 0x90, 0}, {0x52, 0x18, 0}, + {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x88, 0}, {0x57, 0x00, 0}, + {0x5a, 0x90, 0}, {0x5b, 0x18, 0}, {0x5c, 0x05, 0}, {0xc3, 0xef, 0}, + {0x7f, 0x00, 0}, {0xda, 0x01, 0}, {0xe5, 0x1f, 0}, {0xe1, 0x67, 0}, + {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0} +#endif +}; + +static struct reg_value ov2640_setting_800_600[] = { +#ifdef CONFIG_MACH_MX25_3DS + {0xff, 0x01, 0}, {0x12, 0x80, 5}, {0xff, 0x00, 0}, {0x2c, 0xff, 0}, + {0x2e, 0xdf, 0}, {0xff, 0x01, 0}, {0x3c, 0x32, 0}, {0x11, 0x00, 0}, + {0x09, 0x02, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, {0x14, 0x48, 0}, + {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, {0x3b, 0xfb, 0}, + {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, {0x39, 0x92, 0}, + {0x35, 0xda, 0}, {0x22, 0x1a, 0}, {0x37, 0xc3, 0}, {0x23, 0x00, 0}, + {0x34, 0xc0, 0}, {0x36, 0x1a, 0}, {0x06, 0x88, 0}, {0x07, 0xc0, 0}, + {0x0d, 0x87, 0}, {0x0e, 0x41, 0}, {0x4c, 0x00, 0}, + {0x48, 0x00, 0}, {0x5b, 0x00, 0}, {0x42, 0x03, 0}, {0x4a, 0x81, 0}, + {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, {0x26, 0x82, 0}, + {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x22, 0}, {0x0c, 0x3c, 0}, + {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, {0x20, 0x80, 0}, + {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, {0x6e, 0x00, 0}, + {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, {0x12, 0x40, 0}, + {0x17, 0x11, 0}, {0x18, 0x43, 0}, {0x19, 0x00, 0}, {0x1a, 0x4b, 0}, + {0x32, 0x09, 0}, {0x37, 0xc0, 0}, {0x4f, 0xca, 0}, {0x50, 0xa8, 0}, + {0x5a, 0x23, 0}, {0x6d, 0x00, 0}, {0x3d, 0x38, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0xe0, 0x14, 0}, + {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, {0x43, 0x18, 0}, + {0x4c, 0x00, 0}, {0x87, 0xd5, 0}, {0x88, 0x3f, 0}, {0xd7, 0x01, 0}, + {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, {0xc9, 0x80, 0}, + {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, {0x7d, 0x48, 0}, + {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, {0x7d, 0x10, 0}, + {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, {0x91, 0x1a, 0}, + {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, {0x91, 0x75, 0}, + {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, {0x91, 0x96, 0}, + {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, {0x91, 0xd7, 0}, + {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, {0x93, 0x06, 0}, + {0x93, 0xe3, 0}, {0x93, 0x05, 0}, {0x93, 0x05, 0}, {0x93, 0x00, 0}, + {0x93, 0x04, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, {0x97, 0x02, 0}, + {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, {0x97, 0x28, 0}, + {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, {0x97, 0x80, 0}, + {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xc3, 0xed, 0}, {0xa4, 0x00, 0}, + {0xa8, 0x00, 0}, {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, + {0xc7, 0x10, 0}, {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, + {0xb9, 0x7c, 0}, {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, + {0xb0, 0xc5, 0}, {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, + {0xc0, 0x64, 0}, {0xc1, 0x4b, 0}, {0x8c, 0x00, 0}, {0x86, 0x3d, 0}, + {0x50, 0x00, 0}, {0x51, 0xc8, 0}, {0x52, 0x96, 0}, {0x53, 0x00, 0}, + {0x54, 0x00, 0}, {0x55, 0x00, 0}, {0x5a, 0xc8, 0}, {0x5b, 0x96, 0}, + {0x5c, 0x00, 0}, {0xd3, 0x82, 0}, {0xc3, 0xed, 0}, {0x7f, 0x00, 0}, + {0xda, 0x00, 0}, {0xe5, 0x1f, 0}, {0xe1, 0x67, 0}, {0xe0, 0x00, 0}, + {0xdd, 0x7f, 0}, {0x05, 0x00, 0}, {0xff, 0x00, 0}, {0xe0, 0x04, 0}, + {0xc0, 0x64, 0}, {0xc1, 0x4b, 0}, {0x8c, 0x00, 0}, {0x86, 0x3d, 0}, + {0x50, 0x00, 0}, {0x51, 0xc8, 0}, {0x52, 0x96, 0}, {0x53, 0x00, 0}, + {0x54, 0x00, 0}, {0x55, 0x00, 0}, {0x5a, 0xa0, 0}, {0x5b, 0x78, 0}, + {0x5c, 0x00, 0}, {0xd3, 0x82, 0}, {0xe0, 0x00, 1000} +#else + {0xff, 0, 0}, {0xff, 1, 0}, {0x12, 0x80, 1}, {0xff, 00, 0}, + {0x2c, 0xff, 0}, {0x2e, 0xdf, 0}, {0xff, 0x1, 0}, {0x3c, 0x32, 0}, + {0x11, 0x01, 0}, {0x09, 0x00, 0}, {0x04, 0x28, 0}, {0x13, 0xe5, 0}, + {0x14, 0x48, 0}, {0x2c, 0x0c, 0}, {0x33, 0x78, 0}, {0x3a, 0x33, 0}, + {0x3b, 0xfb, 0}, {0x3e, 0x00, 0}, {0x43, 0x11, 0}, {0x16, 0x10, 0}, + {0x39, 0x92, 0}, {0x35, 0xda, 0}, {0x22, 0x1a, 0}, {0x37, 0xc3, 0}, + {0x23, 0x00, 0}, {0x34, 0xc0, 0}, {0x36, 0x1a, 0}, {0x06, 0x88, 0}, + {0x07, 0xc0, 0}, {0x0d, 0x87, 0}, {0x0e, 0x41, 0}, {0x4c, 0x00, 0}, + {0x4a, 0x81, 0}, {0x21, 0x99, 0}, {0x24, 0x40, 0}, {0x25, 0x38, 0}, + {0x26, 0x82, 0}, {0x5c, 0x00, 0}, {0x63, 0x00, 0}, {0x46, 0x22, 0}, + {0x0c, 0x3c, 0}, {0x5d, 0x55, 0}, {0x5e, 0x7d, 0}, {0x5f, 0x7d, 0}, + {0x60, 0x55, 0}, {0x61, 0x70, 0}, {0x62, 0x80, 0}, {0x7c, 0x05, 0}, + {0x20, 0x80, 0}, {0x28, 0x30, 0}, {0x6c, 0x00, 0}, {0x6d, 0x80, 0}, + {0x6e, 00, 0}, {0x70, 0x02, 0}, {0x71, 0x94, 0}, {0x73, 0xc1, 0}, + {0x12, 0x40, 0}, {0x17, 0x11, 0}, {0x18, 0x43, 0}, {0x19, 0x00, 0}, + {0x1a, 0x4b, 0}, {0x32, 0x09, 0}, {0x37, 0xc0, 0}, {0x4f, 0xca, 0}, + {0x50, 0xa8, 0}, {0x6d, 0x00, 0}, {0x3d, 0x38, 0}, {0xff, 0x00, 0}, + {0xe5, 0x7f, 0}, {0xf9, 0xc0, 0}, {0x41, 0x24, 0}, {0x44, 0x06, 0}, + {0xe0, 0x14, 0}, {0x76, 0xff, 0}, {0x33, 0xa0, 0}, {0x42, 0x20, 0}, + {0x43, 0x18, 0}, {0x4c, 0x00, 0}, {0x87, 0xd0, 0}, {0x88, 0x3f, 0}, + {0xd7, 0x03, 0}, {0xd9, 0x10, 0}, {0xd3, 0x82, 0}, {0xc8, 0x08, 0}, + {0xc9, 0x80, 0}, {0x7c, 0x00, 0}, {0x7d, 0x00, 0}, {0x7c, 0x03, 0}, + {0x7d, 0x48, 0}, {0x7d, 0x48, 0}, {0x7c, 0x08, 0}, {0x7d, 0x20, 0}, + {0x7d, 0x10, 0}, {0x7d, 0x0e, 0}, {0x90, 0x00, 0}, {0x91, 0x0e, 0}, + {0x91, 0x1a, 0}, {0x91, 0x31, 0}, {0x91, 0x5a, 0}, {0x91, 0x69, 0}, + {0x91, 0x75, 0}, {0x91, 0x7e, 0}, {0x91, 0x88, 0}, {0x91, 0x8f, 0}, + {0x91, 0x96, 0}, {0x91, 0xa3, 0}, {0x91, 0xaf, 0}, {0x91, 0xc4, 0}, + {0x91, 0xd7, 0}, {0x91, 0xe8, 0}, {0x91, 0x20, 0}, {0x92, 0x00, 0}, + {0x93, 0x06, 0}, {0x93, 0xe3, 0}, {0x93, 0x03, 0}, {0x93, 0x03, 0}, + {0x93, 0x00, 0}, {0x93, 0x02, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, {0x93, 0x00, 0}, + {0x93, 0x00, 0}, {0x96, 0x00, 0}, {0x97, 0x08, 0}, {0x97, 0x19, 0}, + {0x97, 0x02, 0}, {0x97, 0x0c, 0}, {0x97, 0x24, 0}, {0x97, 0x30, 0}, + {0x97, 0x28, 0}, {0x97, 0x26, 0}, {0x97, 0x02, 0}, {0x97, 0x98, 0}, + {0x97, 0x80, 0}, {0x97, 0x00, 0}, {0x97, 0x00, 0}, {0xa4, 0x00, 0}, + {0xa8, 0x00, 0}, {0xc5, 0x11, 0}, {0xc6, 0x51, 0}, {0xbf, 0x80, 0}, + {0xc7, 0x10, 0}, {0xb6, 0x66, 0}, {0xb8, 0xa5, 0}, {0xb7, 0x64, 0}, + {0xb9, 0x7c, 0}, {0xb3, 0xaf, 0}, {0xb4, 0x97, 0}, {0xb5, 0xff, 0}, + {0xb0, 0xc5, 0}, {0xb1, 0x94, 0}, {0xb2, 0x0f, 0}, {0xc4, 0x5c, 0}, + {0xa6, 0x00, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x1b, 0}, + {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, + {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, + {0xa7, 0x18, 0}, {0xa7, 0x20, 0}, {0xa7, 0xd8, 0}, {0xa7, 0x19, 0}, + {0xa7, 0x31, 0}, {0xa7, 0x00, 0}, {0xa7, 0x18, 0}, {0xc0, 0x64, 0}, + {0xc1, 0x4b, 0}, {0x86, 0x1d, 0}, {0x50, 0x00, 0}, {0x51, 0xc8, 0}, + {0x52, 0x96, 0}, {0x53, 0x00, 0}, {0x54, 0x00, 0}, {0x55, 0x00, 0}, + {0x57, 0x00, 0}, {0x5a, 0xc8, 0}, {0x5b, 0x96, 0}, {0x5c, 0x00, 0}, + {0xc3, 0xef, 0}, {0x7f, 0x00, 0}, {0xda, 0x01, 0}, {0xe5, 0x1f, 0}, + {0xe1, 0x67, 0}, {0xe0, 0x00, 0}, {0xdd, 0x7f, 0}, {0x05, 0x00, 0} +#endif +}; + +/*! + * Maintains the information on the current state of the sesor. + */ +struct sensor { + const struct ov2640_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + u32 csi; + u32 mclk; + +} ov2640_data; + +static struct regulator *io_regulator; +static struct regulator *core_regulator; +static struct regulator *analog_regulator; +static struct regulator *gpo_regulator; + +extern void gpio_sensor_active(void); +extern void gpio_sensor_inactive(void); + +/* list of image formats supported by this sensor */ +/* +const static struct v4l2_fmtdesc ov2640_formats[] = { + { + .description = "YUYV (YUV 4:2:2), packed", + .pixelformat = V4L2_PIX_FMT_UYVY, + }, +}; + */ + +static int ov2640_init_mode(struct sensor *s) +{ + int ret = -1; + struct reg_value *setting; + int i, num; + + pr_debug("In ov2640:ov2640_init_mode capturemode is %d\n", + s->streamcap.capturemode); + + if (s->streamcap.capturemode & V4L2_MODE_HIGHQUALITY) { + s->pix.width = 1600; + s->pix.height = 1120; + setting = ov2640_setting_1600_1120; + num = ARRAY_SIZE(ov2640_setting_1600_1120); + } else { + s->pix.width = 800; + s->pix.height = 600; + setting = ov2640_setting_800_600; + num = ARRAY_SIZE(ov2640_setting_800_600); + } + + for (i = 0; i < num; i++) { + ret = i2c_smbus_write_byte_data(s->i2c_client, + setting[i].reg, + setting[i].value); + if (ret < 0) { + pr_err("write reg error: reg=%x, val=%x\n", + setting[i].reg, setting[i].value); + return ret; + } + if (setting[i].delay_ms > 0) + msleep(setting[i].delay_ms); + } + + return ret; +} + +/* At present only support change to 15fps(only for SVGA mode) */ +static int ov2640_set_fps(struct sensor *s, int fps) +{ + int ret = 0; + + if (i2c_smbus_write_byte_data(s->i2c_client, 0xff, 0x01) < 0) { + pr_err("in %s,change to sensor addr failed\n", __func__); + ret = -EPERM; + } + + /* change the camera framerate to 15fps(only for SVGA mode) */ + if (i2c_smbus_write_byte_data(s->i2c_client, 0x11, 0x01) < 0) { + pr_err("change camera to 15fps failed\n"); + ret = -EPERM; + } + + return ret; +} + +static int ov2640_set_format(struct sensor *s, int format) +{ + int ret = 0; + + if (i2c_smbus_write_byte_data(s->i2c_client, 0xff, 0x00) < 0) + ret = -EPERM; + + if (format == V4L2_PIX_FMT_RGB565) { + /* set RGB565 format */ + if (i2c_smbus_write_byte_data(s->i2c_client, 0xda, 0x08) < 0) + ret = -EPERM; + + if (i2c_smbus_write_byte_data(s->i2c_client, 0xd7, 0x03) < 0) + ret = -EPERM; + } else if (format == V4L2_PIX_FMT_YUV420) { + /* set YUV420 format */ + if (i2c_smbus_write_byte_data(s->i2c_client, 0xda, 0x00) < 0) + ret = -EPERM; + + if (i2c_smbus_write_byte_data(s->i2c_client, 0xd7, 0x1b) < 0) + ret = -EPERM; + } else { + pr_debug("format not supported\n"); + } + + return ret; +} + +/* --------------- IOCTL functions from v4l2_int_ioctl_desc --------------- */ + +/*! + * ioctl_g_ifparm - V4L2 sensor interface handler for vidioc_int_g_ifparm_num + * s: pointer to standard V4L2 device structure + * p: pointer to standard V4L2 vidioc_int_g_ifparm_num ioctl structure + * + * Gets slave interface parameters. + * Calculates the required xclk value to support the requested + * clock parameters in p. This value is returned in the p + * parameter. + * + * vidioc_int_g_ifparm returns platform-specific information about the + * interface settings used by the sensor. + * + * Given the image capture format in pix, the nominal frame period in + * timeperframe, calculate the required xclk frequency. + * + * Called on open. + */ +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + pr_debug("In ov2640:ioctl_g_ifparm\n"); + + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + memset(p, 0, sizeof(*p)); + p->u.bt656.clock_curr = ov2640_data.mclk; + p->if_type = V4L2_IF_TYPE_BT656; + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.clock_min = OV2640_XCLK_MIN; + p->u.bt656.clock_max = OV2640_XCLK_MAX; + + return 0; +} + +/*! + * Sets the camera power. + * + * s pointer to the camera device + * on if 1, power is to be turned on. 0 means power is to be turned off + * + * ioctl_s_power - V4L2 sensor interface handler for vidioc_int_s_power_num + * @s: pointer to standard V4L2 device structure + * @on: power state to which device is to be set + * + * Sets devices power state to requrested state, if possible. + * This is called on open, close, suspend and resume. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + pr_debug("In ov2640:ioctl_s_power\n"); + + if (on && !sensor->on) { + gpio_sensor_active(); + if (io_regulator) + if (regulator_enable(io_regulator) != 0) + return -EIO; + if (core_regulator) + if (regulator_enable(core_regulator) != 0) + return -EIO; + if (gpo_regulator) + if (regulator_enable(gpo_regulator) != 0) + return -EIO; + if (analog_regulator) + if (regulator_enable(analog_regulator) != 0) + return -EIO; + } else if (!on && sensor->on) { + if (analog_regulator) + regulator_disable(analog_regulator); + if (core_regulator) + regulator_disable(core_regulator); + if (io_regulator) + regulator_disable(io_regulator); + if (gpo_regulator) + regulator_disable(gpo_regulator); + gpio_sensor_inactive(); + } + + sensor->on = on; + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + int ret = 0; + + pr_debug("In ov2640:ioctl_g_parm\n"); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = sensor->streamcap.capability; + cparm->timeperframe = sensor->streamcap.timeperframe; + cparm->capturemode = sensor->streamcap.capturemode; + ret = 0; + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_err(" type is not V4L2_BUF_TYPE_VIDEO_CAPTURE " \ + "but %d\n", a->type); + ret = -EINVAL; + break; + + default: + pr_err(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe; + u32 tgt_fps; /* target frames per secound */ + int ret = 0; + + pr_debug("In ov2640:ioctl_s_parm\n"); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + + /* Check that the new frame rate is allowed. */ + if ((timeperframe->numerator == 0) + || (timeperframe->denominator == 0)) { + timeperframe->denominator = DEFAULT_FPS; + timeperframe->numerator = 1; + } + tgt_fps = timeperframe->denominator + / timeperframe->numerator; + + if (tgt_fps > MAX_FPS) { + timeperframe->denominator = MAX_FPS; + timeperframe->numerator = 1; + } else if (tgt_fps < MIN_FPS) { + timeperframe->denominator = MIN_FPS; + timeperframe->numerator = 1; + } + sensor->streamcap.timeperframe = *timeperframe; + sensor->streamcap.capturemode = + (u32)a->parm.capture.capturemode; + + ret = ov2640_init_mode(sensor); + if (tgt_fps == 15) + ov2640_set_fps(sensor, tgt_fps); + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_err(" type is not V4L2_BUF_TYPE_VIDEO_CAPTURE " \ + "but %d\n", a->type); + ret = -EINVAL; + break; + + default: + pr_err(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_fmt_cap - V4L2 sensor interface handler for ioctl_s_fmt_cap + * set camera output format and resolution format + * + * @s: pointer to standard V4L2 device structure + * @arg: pointer to parameter, according this to set camera + * + * Returns 0 if set succeed, else return -1 + */ +static int ioctl_s_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + u32 format = f->fmt.pix.pixelformat; + int size = 0, ret = 0; + + size = f->fmt.pix.width * f->fmt.pix.height; + switch (format) { + case V4L2_PIX_FMT_RGB565: + if (size > 640 * 480) + sensor->streamcap.capturemode = V4L2_MODE_HIGHQUALITY; + else + sensor->streamcap.capturemode = 0; + ret = ov2640_init_mode(sensor); + + ret = ov2640_set_format(sensor, V4L2_PIX_FMT_RGB565); + break; + case V4L2_PIX_FMT_UYVY: + if (size > 640 * 480) + sensor->streamcap.capturemode = V4L2_MODE_HIGHQUALITY; + else + sensor->streamcap.capturemode = 0; + ret = ov2640_init_mode(sensor); + break; + case V4L2_PIX_FMT_YUV420: + if (size > 640 * 480) + sensor->streamcap.capturemode = V4L2_MODE_HIGHQUALITY; + else + sensor->streamcap.capturemode = 0; + ret = ov2640_init_mode(sensor); + + /* YUYV: width * 2, YY: width */ + ret = ov2640_set_format(sensor, V4L2_PIX_FMT_YUV420); + break; + default: + pr_debug("case not supported\n"); + break; + } + + return ret; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + + pr_debug("In ov2640:ioctl_g_fmt_cap.\n"); + + f->fmt.pix = sensor->pix; + + return 0; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int ret = 0; + + pr_debug("In ov2640:ioctl_g_ctrl\n"); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + vc->value = ov2640_data.brightness; + break; + case V4L2_CID_HUE: + vc->value = ov2640_data.hue; + break; + case V4L2_CID_CONTRAST: + vc->value = ov2640_data.contrast; + break; + case V4L2_CID_SATURATION: + vc->value = ov2640_data.saturation; + break; + case V4L2_CID_RED_BALANCE: + vc->value = ov2640_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + vc->value = ov2640_data.blue; + break; + case V4L2_CID_EXPOSURE: + vc->value = ov2640_data.ae_mode; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + + pr_debug("In ov2640:ioctl_s_ctrl %d\n", vc->id); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + pr_debug(" V4L2_CID_BRIGHTNESS\n"); + break; + case V4L2_CID_CONTRAST: + pr_debug(" V4L2_CID_CONTRAST\n"); + break; + case V4L2_CID_SATURATION: + pr_debug(" V4L2_CID_SATURATION\n"); + break; + case V4L2_CID_HUE: + pr_debug(" V4L2_CID_HUE\n"); + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_AUTO_WHITE_BALANCE\n"); + break; + case V4L2_CID_DO_WHITE_BALANCE: + pr_debug( + " V4L2_CID_DO_WHITE_BALANCE\n"); + break; + case V4L2_CID_RED_BALANCE: + pr_debug(" V4L2_CID_RED_BALANCE\n"); + break; + case V4L2_CID_BLUE_BALANCE: + pr_debug(" V4L2_CID_BLUE_BALANCE\n"); + break; + case V4L2_CID_GAMMA: + pr_debug(" V4L2_CID_GAMMA\n"); + break; + case V4L2_CID_EXPOSURE: + pr_debug(" V4L2_CID_EXPOSURE\n"); + break; + case V4L2_CID_AUTOGAIN: + pr_debug(" V4L2_CID_AUTOGAIN\n"); + break; + case V4L2_CID_GAIN: + pr_debug(" V4L2_CID_GAIN\n"); + break; + case V4L2_CID_HFLIP: + pr_debug(" V4L2_CID_HFLIP\n"); + break; + case V4L2_CID_VFLIP: + pr_debug(" V4L2_CID_VFLIP\n"); + break; + default: + pr_debug(" Default case\n"); + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + pr_debug("In ov2640:ioctl_init\n"); + + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + struct sensor *sensor = s->priv; + u32 tgt_xclk; /* target xclk */ + + pr_debug("In ov2640:ioctl_dev_init\n"); + + gpio_sensor_active(); + ov2640_data.on = true; + + tgt_xclk = ov2640_data.mclk; + tgt_xclk = min(tgt_xclk, (u32)OV2640_XCLK_MAX); + tgt_xclk = max(tgt_xclk, (u32)OV2640_XCLK_MIN); + ov2640_data.mclk = tgt_xclk; + + pr_debug(" Setting mclk to %d MHz\n", + tgt_xclk / 1000000); + set_mclk_rate(&ov2640_data.mclk); + + return ov2640_init_mode(sensor); +} + +/*! + * ioctl_dev_exit - V4L2 sensor interface handler for vidioc_int_dev_exit_num + * @s: pointer to standard V4L2 device structure + * + * Delinitialise the device when slave detaches to the master. + */ +static int ioctl_dev_exit(struct v4l2_int_device *s) +{ + pr_debug("In ov2640:ioctl_dev_exit\n"); + + gpio_sensor_inactive(); + + return 0; +} + +/*! + * This structure defines all the ioctls for this module and links them to the + * enumeration. + */ +static struct v4l2_int_ioctl_desc ov2640_ioctl_desc[] = { + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, + {vidioc_int_dev_exit_num, (v4l2_int_ioctl_func*)ioctl_dev_exit}, + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *)ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *)ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *)ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *)ioctl_init}, +/* {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap}, */ +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap}, */ + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_g_fmt_cap}, + {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func*)ioctl_s_fmt_cap}, + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *)ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *)ioctl_s_parm}, +/* {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *)ioctl_queryctrl}, */ + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *)ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *)ioctl_s_ctrl}, +}; + +static struct v4l2_int_slave ov2640_slave = { + .ioctls = ov2640_ioctl_desc, + .num_ioctls = ARRAY_SIZE(ov2640_ioctl_desc), +}; + +static struct v4l2_int_device ov2640_int_device = { + .module = THIS_MODULE, + .name = "ov2640", + .type = v4l2_int_type_slave, + .u = { + .slave = &ov2640_slave, + }, +}; + +/*! + * ov2640 I2C attach function + * Function set in i2c_driver struct. + * Called by insmod ov2640_camera.ko. + * + * @param client struct i2c_client* + * @return Error code indicating success or failure + */ +static int ov2640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + struct mxc_camera_platform_data *plat_data = client->dev.platform_data; + + pr_debug("In ov2640_probe (RH_BT565)\n"); + + /* Set initial values for the sensor struct. */ + memset(&ov2640_data, 0, sizeof(ov2640_data)); + ov2640_data.i2c_client = client; + ov2640_data.mclk = 24000000; + ov2640_data.mclk = plat_data->mclk; + ov2640_data.pix.pixelformat = V4L2_PIX_FMT_UYVY; + ov2640_data.pix.width = 800; + ov2640_data.pix.height = 600; + ov2640_data.streamcap.capability = V4L2_MODE_HIGHQUALITY + | V4L2_CAP_TIMEPERFRAME; + ov2640_data.streamcap.capturemode = 0; + ov2640_data.streamcap.timeperframe.denominator = DEFAULT_FPS; + ov2640_data.streamcap.timeperframe.numerator = 1; + + if (plat_data->io_regulator) { + io_regulator = + regulator_get(&client->dev, plat_data->io_regulator); + if (!IS_ERR(io_regulator)) { + regulator_set_voltage(io_regulator, 2800000, 2800000); + if (regulator_enable(io_regulator) != 0) { + pr_err("%s:io set voltage error\n", __func__); + goto err1; + } else { + dev_dbg(&client->dev, + "%s:io set voltage ok\n", __func__); + } + } else + io_regulator = NULL; + } + + if (plat_data->core_regulator) { + core_regulator = + regulator_get(&client->dev, plat_data->core_regulator); + if (!IS_ERR(core_regulator)) { + regulator_set_voltage(core_regulator, + 1300000, 1300000); + if (regulator_enable(core_regulator) != 0) { + pr_err("%s:core set voltage error\n", __func__); + goto err2; + } else { + dev_dbg(&client->dev, + "%s:core set voltage ok\n", __func__); + } + } else + core_regulator = NULL; + } + + if (plat_data->analog_regulator) { + analog_regulator = + regulator_get(&client->dev, plat_data->analog_regulator); + if (!IS_ERR(analog_regulator)) { + regulator_set_voltage(analog_regulator, 2000000, 2000000); + if (regulator_enable(analog_regulator) != 0) { + pr_err("%s:analog set voltage error\n", + __func__); + goto err3; + } else { + dev_dbg(&client->dev, + "%s:analog set voltage ok\n", __func__); + } + } else + analog_regulator = NULL; + } + + if (plat_data->gpo_regulator) { + gpo_regulator = + regulator_get(&client->dev, plat_data->gpo_regulator); + if (!IS_ERR(gpo_regulator)) { + if (regulator_enable(gpo_regulator) != 0) { + pr_err("%s:gpo3 set voltage error\n", __func__); + goto err4; + } else { + dev_dbg(&client->dev, + "%s:gpo3 set voltage ok\n", __func__); + } + } else + gpo_regulator = NULL; + } + + /* This function attaches this structure to the /dev/video0 device. + * The pointer in priv points to the ov2640_data structure here.*/ + ov2640_int_device.priv = &ov2640_data; + retval = v4l2_int_device_register(&ov2640_int_device); + + return retval; + +err4: + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } +err3: + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } +err2: + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } +err1: + return -1; +} + +/*! + * ov2640 I2C detach function + * Called on rmmod ov2640_camera.ko + * + * @param client struct i2c_client* + * @return Error code indicating success or failure + */ +static int ov2640_remove(struct i2c_client *client) +{ + pr_debug("In ov2640_remove\n"); + + v4l2_int_device_unregister(&ov2640_int_device); + + if (gpo_regulator) { + regulator_disable(gpo_regulator); + regulator_put(gpo_regulator); + } + + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } + + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } + + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } + + return 0; +} + +static const struct i2c_device_id ov2640_id[] = { + {"ov2640", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov2640_id); + +static struct i2c_driver ov2640_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ov2640", + }, + .probe = ov2640_probe, + .remove = ov2640_remove, + .id_table = ov2640_id, +/* To add power management add .suspend and .resume functions */ +}; + +/*! + * ov2640 init function + * Called by insmod ov2640_camera.ko. + * + * @return Error code indicating success or failure + */ +static __init int ov2640_init(void) +{ + u8 err; + + pr_debug("In ov2640_init\n"); + + err = i2c_add_driver(&ov2640_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * OV2640 cleanup function + * Called on rmmod ov2640_camera.ko + * + * @return Error code indicating success or failure + */ +static void __exit ov2640_clean(void) +{ + pr_debug("In ov2640_clean\n"); + i2c_del_driver(&ov2640_i2c_driver); +} + +module_init(ov2640_init); +module_exit(ov2640_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OV2640 Camera Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/capture/ov3640.c b/drivers/media/video/mxc/capture/ov3640.c new file mode 100644 index 000000000000..899945b7d071 --- /dev/null +++ b/drivers/media/video/mxc/capture/ov3640.c @@ -0,0 +1,1432 @@ +/* + * Copyright 2005-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> +#include <mach/hardware.h> +#include <media/v4l2-int-device.h> +#include "mxc_v4l2_capture.h" + +#define OV3640_VOLTAGE_ANALOG 2800000 +#define OV3640_VOLTAGE_DIGITAL_CORE 1500000 +#define OV3640_VOLTAGE_DIGITAL_IO 1800000 +#define OV3640_VOLTAGE_DIGITAL_GPO 2800000 + +/* Check these values! */ +#define MIN_FPS 15 +#define MAX_FPS 30 +#define DEFAULT_FPS 30 + +#define OV3640_XCLK_MIN 6000000 +#define OV3640_XCLK_MAX 24000000 + +enum ov3640_mode { + ov3640_mode_MIN = 0, + ov3640_mode_VGA_640_480 = 0, + ov3640_mode_QVGA_320_240 = 1, + ov3640_mode_XGA_1024_768 = 2, + ov3640_mode_QXGA_2048_1536 = 3, + ov3640_mode_NTSC_720_480 = 4, + ov3640_mode_PAL_720_576 = 5, + ov3640_mode_MAX = 5 +}; + +enum ov3640_frame_rate { + ov3640_15_fps, + ov3640_30_fps +}; + +struct reg_value { + u16 u16RegAddr; + u8 u8Val; + u8 u8Mask; + u32 u32Delay_ms; +}; + +struct ov3640_mode_info { + enum ov3640_mode mode; + u32 width; + u32 height; + struct reg_value *init_data_ptr; + u32 init_data_size; +}; + +/*! + * Maintains the information on the current state of the sesor. + */ +struct sensor { + const struct ov3640_platform_data *platform_data; + struct v4l2_int_device *v4l2_int_device; + struct i2c_client *i2c_client; + struct v4l2_pix_format pix; + struct v4l2_captureparm streamcap; + bool on; + + /* control settings */ + int brightness; + int hue; + int contrast; + int saturation; + int red; + int green; + int blue; + int ae_mode; + + u32 mclk; + int csi; +} ov3640_data; + +static struct reg_value ov3640_setting_15fps_QXGA_2048_1536[] = { +#if 0 + /* The true 15fps QXGA setting. */ + {0x3012, 0x80, 0, 0}, {0x304d, 0x41, 0, 0}, {0x3087, 0x16, 0, 0}, + {0x30aa, 0x45, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x13, 0, 0}, {0x30d7, 0x10, 0, 0}, {0x309e, 0x00, 0, 0}, + {0x3602, 0x26, 0, 0}, {0x3603, 0x4D, 0, 0}, {0x364c, 0x04, 0, 0}, + {0x360c, 0x12, 0, 0}, {0x361e, 0x00, 0, 0}, {0x361f, 0x11, 0, 0}, + {0x3633, 0x03, 0, 0}, {0x3629, 0x3c, 0, 0}, {0x300e, 0x33, 0, 0}, + {0x300f, 0x21, 0, 0}, {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, + {0x304c, 0x81, 0, 0}, {0x3029, 0x47, 0, 0}, {0x3070, 0x00, 0, 0}, + {0x3071, 0xEC, 0, 0}, {0x301C, 0x06, 0, 0}, {0x3072, 0x00, 0, 0}, + {0x3073, 0xC5, 0, 0}, {0x301D, 0x07, 0, 0}, {0x3018, 0x38, 0, 0}, + {0x3019, 0x30, 0, 0}, {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, + {0x3087, 0x02, 0, 0}, {0x3082, 0x20, 0, 0}, {0x303c, 0x08, 0, 0}, + {0x303d, 0x18, 0, 0}, {0x303e, 0x06, 0, 0}, {0x303F, 0x0c, 0, 0}, + {0x3030, 0x62, 0, 0}, {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, + {0x3033, 0x6e, 0, 0}, {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, + {0x3036, 0xa6, 0, 0}, {0x3037, 0x6a, 0, 0}, {0x3015, 0x12, 0, 0}, + {0x3014, 0x04, 0, 0}, {0x3013, 0xf7, 0, 0}, {0x3104, 0x02, 0, 0}, + {0x3105, 0xfd, 0, 0}, {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, + {0x3308, 0xa5, 0, 0}, {0x3316, 0xff, 0, 0}, {0x3317, 0x00, 0, 0}, + {0x3087, 0x02, 0, 0}, {0x3082, 0x20, 0, 0}, {0x3300, 0x13, 0, 0}, + {0x3301, 0xd6, 0, 0}, {0x3302, 0xef, 0, 0}, {0x30b8, 0x20, 0, 0}, + {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3304, 0x00, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x02, 0, 0}, {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, + {0x3022, 0x00, 0, 0}, {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, + {0x3025, 0x18, 0, 0}, {0x3026, 0x06, 0, 0}, {0x3027, 0x0c, 0, 0}, + {0x335f, 0x68, 0, 0}, {0x3360, 0x18, 0, 0}, {0x3361, 0x0c, 0, 0}, + {0x3362, 0x68, 0, 0}, {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, + {0x3403, 0x42, 0, 0}, {0x3088, 0x08, 0, 0}, {0x3089, 0x00, 0, 0}, + {0x308a, 0x06, 0, 0}, {0x308b, 0x00, 0, 0}, {0x3507, 0x06, 0, 0}, + {0x350a, 0x4f, 0, 0}, {0x3600, 0xc4, 0, 0}, +#endif + /* + * Only support 7.5fps for QXGA to workaround screen tearing issue + * for 15fps when capturing still image. + */ + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, + {0x30bb, 0x08, 0, 0}, {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3301, 0xde, 0, 0}, {0x3304, 0x00, 0, 0}, + {0x3400, 0x00, 0, 0}, {0x3404, 0x02, 0, 0}, {0x3600, 0xc4, 0, 0}, + {0x3088, 0x08, 0, 0}, {0x3089, 0x00, 0, 0}, {0x308a, 0x06, 0, 0}, + {0x308b, 0x00, 0, 0}, {0x308d, 0x04, 0, 0}, {0x3086, 0x03, 0, 0}, + {0x3086, 0x00, 0, 0}, {0x3011, 0x01, 0, 0}, +}; + +static struct reg_value ov3640_setting_15fps_XGA_1024_768[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, + {0x30bb, 0x08, 0, 0}, {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3301, 0xde, 0, 0}, {0x3304, 0x00, 0, 0}, + {0x3400, 0x01, 0, 0}, {0x3404, 0x1d, 0, 0}, {0x3600, 0xc4, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, + {0x3022, 0x00, 0, 0}, {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, + {0x3025, 0x00, 0, 0}, {0x3026, 0x06, 0, 0}, {0x3027, 0x00, 0, 0}, + {0x335f, 0x68, 0, 0}, {0x3360, 0x00, 0, 0}, {0x3361, 0x00, 0, 0}, + {0x3362, 0x34, 0, 0}, {0x3363, 0x00, 0, 0}, {0x3364, 0x00, 0, 0}, + {0x3403, 0x00, 0, 0}, {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, + {0x308a, 0x03, 0, 0}, {0x308b, 0x00, 0, 0}, {0x307c, 0x10, 0, 0}, + {0x3090, 0xc0, 0, 0}, {0x304c, 0x84, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3011, 0x01, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_XGA_1024_768[] = { + {0x0, 0x0, 0} +}; + +static struct reg_value ov3640_setting_15fps_VGA_640_480[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, + {0x30bb, 0x08, 0, 0}, {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3301, 0xde, 0, 0}, {0x3304, 0x00, 0, 0}, + {0x3400, 0x00, 0, 0}, {0x3404, 0x42, 0, 0}, {0x3600, 0xc4, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, + {0x3022, 0x00, 0, 0}, {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, + {0x3025, 0x00, 0, 0}, {0x3026, 0x06, 0, 0}, {0x3027, 0x00, 0, 0}, + {0x335f, 0x68, 0, 0}, {0x3360, 0x00, 0, 0}, {0x3361, 0x00, 0, 0}, + {0x3362, 0x12, 0, 0}, {0x3363, 0x80, 0, 0}, {0x3364, 0xe0, 0, 0}, + {0x3403, 0x00, 0, 0}, {0x3088, 0x02, 0, 0}, {0x3089, 0x80, 0, 0}, + {0x308a, 0x01, 0, 0}, {0x308b, 0xe0, 0, 0}, {0x307c, 0x10, 0, 0}, + {0x3090, 0xc0, 0, 0}, {0x304c, 0x84, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3011, 0x00, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_VGA_640_480[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x0c, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, + {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0x10, 0x30, 0}, {0x3304, 0x00, 0x03, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x02, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3012, 0x10, 0, 0}, + {0x3023, 0x06, 0, 0}, {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, + {0x302a, 0x03, 0, 0}, {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, + {0x300d, 0x01, 0, 0}, {0x30d7, 0x80, 0x80, 0}, {0x3069, 0x00, 0x40, 0}, + {0x303e, 0x00, 0, 0}, {0x303f, 0xc0, 0, 0}, {0x3302, 0x20, 0x20, 0}, + {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, + {0x3362, 0x12, 0, 0}, {0x3363, 0x88, 0, 0}, {0x3364, 0xe4, 0, 0}, + {0x3403, 0x42, 0, 0}, {0x3088, 0x02, 0, 0}, {0x3089, 0x80, 0, 0}, + {0x308a, 0x01, 0, 0}, {0x308b, 0xe0, 0, 0}, {0x3362, 0x12, 0, 0}, + {0x3363, 0x88, 0, 0}, {0x3364, 0xe4, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x02, 0, 0}, {0x3089, 0x80, 0, 0}, {0x308a, 0x01, 0, 0}, + {0x308b, 0xe0, 0, 0}, {0x300e, 0x37, 0, 0}, {0x300f, 0xe1, 0, 0}, + {0x3010, 0x22, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x84, 0, 0}, + {0x3014, 0x04, 0, 0}, {0x3015, 0x02, 0, 0}, {0x302e, 0x00, 0, 0}, + {0x302d, 0x00, 0, 0}, +}; + +static struct reg_value ov3640_setting_15fps_QVGA_320_240[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x00, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x04, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, + {0x30bb, 0x08, 0, 0}, {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, + {0x3100, 0x02, 0, 0}, {0x3301, 0xde, 0, 0}, {0x3304, 0x00, 0, 0}, + {0x3400, 0x00, 0, 0}, {0x3404, 0x42, 0, 0}, {0x3600, 0xc4, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x3020, 0x01, 0, 0}, {0x3021, 0x1d, 0, 0}, + {0x3022, 0x00, 0, 0}, {0x3023, 0x0a, 0, 0}, {0x3024, 0x08, 0, 0}, + {0x3025, 0x00, 0, 0}, {0x3026, 0x06, 0, 0}, {0x3027, 0x00, 0, 0}, + {0x335f, 0x68, 0, 0}, {0x3360, 0x00, 0, 0}, {0x3361, 0x00, 0, 0}, + {0x3362, 0x01, 0, 0}, {0x3363, 0x40, 0, 0}, {0x3364, 0xf0, 0, 0}, + {0x3403, 0x00, 0, 0}, {0x3088, 0x01, 0, 0}, {0x3089, 0x40, 0, 0}, + {0x308a, 0x00, 0, 0}, {0x308b, 0xf0, 0, 0}, {0x307c, 0x10, 0, 0}, + {0x3090, 0xc0, 0, 0}, {0x304c, 0x84, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3011, 0x01, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_QVGA_320_240[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309c, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x30d7, 0x10, 0, 0}, {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, + {0x3016, 0x82, 0, 0}, {0x3018, 0x38, 0, 0}, {0x3019, 0x30, 0, 0}, + {0x301a, 0x61, 0, 0}, {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, + {0x3082, 0x20, 0, 0}, {0x3015, 0x12, 0, 0}, {0x3014, 0x0c, 0, 0}, + {0x3013, 0xf7, 0, 0}, {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, + {0x303e, 0x06, 0, 0}, {0x303f, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, + {0x3031, 0x26, 0, 0}, {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, + {0x3034, 0xea, 0, 0}, {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, + {0x3037, 0x6a, 0, 0}, {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, + {0x3106, 0x00, 0, 0}, {0x3107, 0xff, 0, 0}, {0x3300, 0x12, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, + {0x3314, 0x42, 0, 0}, {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, + {0x3310, 0xd0, 0, 0}, {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, + {0x330d, 0x18, 0, 0}, {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, + {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, + {0x336a, 0x52, 0, 0}, {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x30b8, 0x20, 0, 0}, {0x30b9, 0x17, 0, 0}, + {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0x10, 0x30, 0}, {0x3304, 0x00, 0x03, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x02, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x308d, 0x04, 0, 0}, + {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, {0x3012, 0x10, 0, 0}, + {0x3023, 0x06, 0, 0}, {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, + {0x302a, 0x03, 0, 0}, {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, + {0x300d, 0x01, 0, 0}, {0x30d7, 0x80, 0x80, 0}, {0x3069, 0x00, 0x40, 0}, + {0x303e, 0x00, 0, 0}, {0x303f, 0xc0, 0, 0}, {0x3302, 0x20, 0x20, 0}, + {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, + {0x3362, 0x34, 0, 0}, {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, + {0x3403, 0x42, 0, 0}, {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, + {0x308a, 0x03, 0, 0}, {0x308b, 0x00, 0, 0}, {0x3362, 0x12, 0, 0}, + {0x3363, 0x88, 0, 0}, {0x3364, 0xe4, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x02, 0, 0}, {0x3089, 0x80, 0, 0}, {0x308a, 0x01, 0, 0}, + {0x308b, 0xe0, 0, 0}, {0x300e, 0x37, 0, 0}, {0x300f, 0xe1, 0, 0}, + {0x3010, 0x22, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x84, 0, 0}, +}; + +static struct reg_value ov3640_setting_15fps_NTSC_720_480[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309C, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x304c, 0x81, 0, 0}, {0x30d7, 0x10, 0, 0}, + {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, {0x3016, 0x82, 0, 0}, + {0x3018, 0x48, 0, 0}, {0x3019, 0x40, 0, 0}, {0x301a, 0x82, 0, 0}, + {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, {0x3082, 0x20, 0, 0}, + {0x3015, 0x12, 0, 0}, {0x3014, 0x84, 0, 0}, {0x3013, 0xf7, 0, 0}, + {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, {0x303e, 0x06, 0, 0}, + {0x303F, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, {0x3031, 0x26, 0, 0}, + {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, {0x3034, 0xea, 0, 0}, + {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, {0x3037, 0x6a, 0, 0}, + {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, {0x3106, 0x00, 0, 0}, + {0x3107, 0xff, 0, 0}, {0x3300, 0x13, 0, 0}, {0x3301, 0xde, 0, 0}, + {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, {0x3314, 0x42, 0, 0}, + {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, {0x3310, 0xd0, 0, 0}, + {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, + {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, {0x330b, 0x1c, 0, 0}, + {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, {0x336a, 0x52, 0, 0}, + {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, {0x30b8, 0x20, 0, 0}, + {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, + {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x3088, 0x08, 0, 0}, + {0x3089, 0x00, 0, 0}, {0x308a, 0x06, 0, 0}, {0x308b, 0x00, 0, 0}, + {0x308d, 0x04, 0, 0}, {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, + {0x30a9, 0xb5, 0, 0}, {0x3317, 0x04, 0, 0}, {0x3316, 0xf8, 0, 0}, + {0x3312, 0x17, 0, 0}, {0x3314, 0x30, 0, 0}, {0x3313, 0x23, 0, 0}, + {0x3315, 0x3e, 0, 0}, {0x3311, 0x9e, 0, 0}, {0x3310, 0xc0, 0, 0}, + {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, {0x330e, 0x5e, 0, 0}, + {0x330f, 0x6c, 0, 0}, {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, + {0x3307, 0x11, 0, 0}, {0x3308, 0x25, 0, 0}, {0x3340, 0x20, 0, 0}, + {0x3341, 0x50, 0, 0}, {0x3342, 0x18, 0, 0}, {0x3343, 0x23, 0, 0}, + {0x3344, 0xad, 0, 0}, {0x3345, 0xd0, 0, 0}, {0x3346, 0xb8, 0, 0}, + {0x3347, 0xb4, 0, 0}, {0x3348, 0x04, 0, 0}, {0x3349, 0x98, 0, 0}, + {0x3355, 0x02, 0, 0}, {0x3358, 0x44, 0, 0}, {0x3359, 0x44, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x3367, 0x23, 0, 0}, {0x3368, 0xBB, 0, 0}, + {0x3369, 0xD6, 0, 0}, {0x336A, 0x2A, 0, 0}, {0x336B, 0x07, 0, 0}, + {0x336C, 0x00, 0, 0}, {0x336D, 0x23, 0, 0}, {0x336E, 0xC3, 0, 0}, + {0x336F, 0xDE, 0, 0}, {0x3370, 0x2b, 0, 0}, {0x3371, 0x07, 0, 0}, + {0x3372, 0x00, 0, 0}, {0x3373, 0x23, 0, 0}, {0x3374, 0x9e, 0, 0}, + {0x3375, 0xD6, 0, 0}, {0x3376, 0x29, 0, 0}, {0x3377, 0x07, 0, 0}, + {0x3378, 0x00, 0, 0}, {0x332a, 0x1d, 0, 0}, {0x331b, 0x08, 0, 0}, + {0x331c, 0x16, 0, 0}, {0x331d, 0x2d, 0, 0}, {0x331e, 0x54, 0, 0}, + {0x331f, 0x66, 0, 0}, {0x3320, 0x73, 0, 0}, {0x3321, 0x80, 0, 0}, + {0x3322, 0x8c, 0, 0}, {0x3323, 0x95, 0, 0}, {0x3324, 0x9d, 0, 0}, + {0x3325, 0xac, 0, 0}, {0x3326, 0xb8, 0, 0}, {0x3327, 0xcc, 0, 0}, + {0x3328, 0xdd, 0, 0}, {0x3329, 0xee, 0, 0}, {0x332e, 0x04, 0, 0}, + {0x332f, 0x04, 0, 0}, {0x3331, 0x02, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3610, 0x40, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x307C, 0x10, 0, 0}, {0x3012, 0x10, 0, 0}, {0x3023, 0x06, 0, 0}, + {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, {0x302a, 0x03, 0, 0}, + {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, {0x300d, 0x01, 0, 0}, + {0x30d7, 0x90, 0, 0}, {0x3069, 0x04, 0, 0}, {0x303e, 0x00, 0, 0}, + {0x303f, 0xc0, 0, 0}, {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, + {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, {0x3362, 0x34, 0, 0}, + {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, {0x308a, 0x03, 0, 0}, + {0x308b, 0x00, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, + {0x3361, 0x04, 0, 0}, {0x3362, 0x23, 0, 0}, {0x3363, 0x28, 0, 0}, + {0x3364, 0x5c, 0, 0}, {0x3403, 0x42, 0, 0}, {0x3088, 0x02, 0, 0}, + {0x3089, 0xD0, 0, 0}, {0x308a, 0x01, 0, 0}, {0x308b, 0xe0, 0, 0}, + {0x304c, 0x83, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3011, 0x00, 0, 0}, {0x3010, 0x81, 0, 0}, {0x302e, 0x00, 0, 0}, + {0x302d, 0x00, 0, 0}, {0x3071, 0xeb, 0, 0}, {0x301C, 0x02, 0, 0}, + {0x3404, 0x02, 0, 0}, +}; + +static struct reg_value ov3640_setting_15fps_PAL_720_576[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309C, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x304c, 0x81, 0, 0}, {0x30d7, 0x10, 0, 0}, + {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, {0x3016, 0x82, 0, 0}, + {0x3018, 0x48, 0, 0}, {0x3019, 0x40, 0, 0}, {0x301a, 0x82, 0, 0}, + {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, {0x3082, 0x20, 0, 0}, + {0x3015, 0x12, 0, 0}, {0x3014, 0x84, 0, 0}, {0x3013, 0xf7, 0, 0}, + {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, {0x303e, 0x06, 0, 0}, + {0x303F, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, {0x3031, 0x26, 0, 0}, + {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, {0x3034, 0xea, 0, 0}, + {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, {0x3037, 0x6a, 0, 0}, + {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, {0x3106, 0x00, 0, 0}, + {0x3107, 0xff, 0, 0}, {0x3300, 0x13, 0, 0}, {0x3301, 0xde, 0, 0}, + {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, {0x3314, 0x42, 0, 0}, + {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, {0x3310, 0xd0, 0, 0}, + {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, + {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, {0x330b, 0x1c, 0, 0}, + {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, {0x336a, 0x52, 0, 0}, + {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, {0x30b8, 0x20, 0, 0}, + {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, + {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x3088, 0x08, 0, 0}, + {0x3089, 0x00, 0, 0}, {0x308a, 0x06, 0, 0}, {0x308b, 0x00, 0, 0}, + {0x308d, 0x04, 0, 0}, {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, + {0x30a9, 0xb5, 0, 0}, {0x3317, 0x04, 0, 0}, {0x3316, 0xf8, 0, 0}, + {0x3312, 0x17, 0, 0}, {0x3314, 0x30, 0, 0}, {0x3313, 0x23, 0, 0}, + {0x3315, 0x3e, 0, 0}, {0x3311, 0x9e, 0, 0}, {0x3310, 0xc0, 0, 0}, + {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, {0x330e, 0x5e, 0, 0}, + {0x330f, 0x6c, 0, 0}, {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, + {0x3307, 0x11, 0, 0}, {0x3308, 0x25, 0, 0}, {0x3340, 0x20, 0, 0}, + {0x3341, 0x50, 0, 0}, {0x3342, 0x18, 0, 0}, {0x3343, 0x23, 0, 0}, + {0x3344, 0xad, 0, 0}, {0x3345, 0xd0, 0, 0}, {0x3346, 0xb8, 0, 0}, + {0x3347, 0xb4, 0, 0}, {0x3348, 0x04, 0, 0}, {0x3349, 0x98, 0, 0}, + {0x3355, 0x02, 0, 0}, {0x3358, 0x44, 0, 0}, {0x3359, 0x44, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x3367, 0x23, 0, 0}, {0x3368, 0xBB, 0, 0}, + {0x3369, 0xD6, 0, 0}, {0x336A, 0x2A, 0, 0}, {0x336B, 0x07, 0, 0}, + {0x336C, 0x00, 0, 0}, {0x336D, 0x23, 0, 0}, {0x336E, 0xC3, 0, 0}, + {0x336F, 0xDE, 0, 0}, {0x3370, 0x2b, 0, 0}, {0x3371, 0x07, 0, 0}, + {0x3372, 0x00, 0, 0}, {0x3373, 0x23, 0, 0}, {0x3374, 0x9e, 0, 0}, + {0x3375, 0xD6, 0, 0}, {0x3376, 0x29, 0, 0}, {0x3377, 0x07, 0, 0}, + {0x3378, 0x00, 0, 0}, {0x332a, 0x1d, 0, 0}, {0x331b, 0x08, 0, 0}, + {0x331c, 0x16, 0, 0}, {0x331d, 0x2d, 0, 0}, {0x331e, 0x54, 0, 0}, + {0x331f, 0x66, 0, 0}, {0x3320, 0x73, 0, 0}, {0x3321, 0x80, 0, 0}, + {0x3322, 0x8c, 0, 0}, {0x3323, 0x95, 0, 0}, {0x3324, 0x9d, 0, 0}, + {0x3325, 0xac, 0, 0}, {0x3326, 0xb8, 0, 0}, {0x3327, 0xcc, 0, 0}, + {0x3328, 0xdd, 0, 0}, {0x3329, 0xee, 0, 0}, {0x332e, 0x04, 0, 0}, + {0x332f, 0x04, 0, 0}, {0x3331, 0x02, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3610, 0x40, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x307C, 0x10, 0, 0}, {0x3012, 0x10, 0, 0}, {0x3023, 0x06, 0, 0}, + {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, {0x302a, 0x03, 0, 0}, + {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, {0x300d, 0x01, 0, 0}, + {0x30d7, 0x90, 0, 0}, {0x3069, 0x04, 0, 0}, {0x303e, 0x00, 0, 0}, + {0x303f, 0xc0, 0, 0}, {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, + {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, {0x3362, 0x34, 0, 0}, + {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, {0x308a, 0x03, 0, 0}, + {0x308b, 0x00, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, + {0x3361, 0x04, 0, 0}, {0x3362, 0x23, 0, 0}, {0x3363, 0x28, 0, 0}, + {0x3364, 0x5c, 0, 0}, {0x3403, 0x42, 0, 0}, {0x3088, 0x02, 0, 0}, + {0x3089, 0xD0, 0, 0}, {0x308a, 0x02, 0, 0}, {0x308b, 0x40, 0, 0}, + {0x304c, 0x83, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3011, 0x00, 0, 0}, {0x3010, 0x81, 0, 0}, {0x302e, 0x00, 0, 0}, + {0x302d, 0x00, 0, 0}, {0x3071, 0xeb, 0, 0}, {0x301C, 0x02, 0, 0}, + {0x3404, 0x02, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_NTSC_720_480[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3087, 0x16, 0, 0}, {0x309C, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x304c, 0x81, 0, 0}, {0x30d7, 0x10, 0, 0}, + {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, {0x3016, 0x82, 0, 0}, + {0x3018, 0x48, 0, 0}, {0x3019, 0x40, 0, 0}, {0x301a, 0x82, 0, 0}, + {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, {0x3082, 0x20, 0, 0}, + {0x3015, 0x12, 0, 0}, {0x3014, 0x84, 0, 0}, {0x3013, 0xf7, 0, 0}, + {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, {0x303e, 0x06, 0, 0}, + {0x303F, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, {0x3031, 0x26, 0, 0}, + {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, {0x3034, 0xea, 0, 0}, + {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, {0x3037, 0x6a, 0, 0}, + {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, {0x3106, 0x00, 0, 0}, + {0x3107, 0xff, 0, 0}, {0x3300, 0x13, 0, 0}, {0x3301, 0xde, 0, 0}, + {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, {0x3314, 0x42, 0, 0}, + {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, {0x3310, 0xd0, 0, 0}, + {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, + {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, {0x330b, 0x1c, 0, 0}, + {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, {0x336a, 0x52, 0, 0}, + {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, {0x30b8, 0x20, 0, 0}, + {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, + {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x3088, 0x08, 0, 0}, + {0x3089, 0x00, 0, 0}, {0x308a, 0x06, 0, 0}, {0x308b, 0x00, 0, 0}, + {0x308d, 0x04, 0, 0}, {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, + {0x30a9, 0xb5, 0, 0}, {0x3317, 0x04, 0, 0}, {0x3316, 0xf8, 0, 0}, + {0x3312, 0x17, 0, 0}, {0x3314, 0x30, 0, 0}, {0x3313, 0x23, 0, 0}, + {0x3315, 0x3e, 0, 0}, {0x3311, 0x9e, 0, 0}, {0x3310, 0xc0, 0, 0}, + {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, {0x330e, 0x5e, 0, 0}, + {0x330f, 0x6c, 0, 0}, {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, + {0x3307, 0x11, 0, 0}, {0x3308, 0x25, 0, 0}, {0x3340, 0x20, 0, 0}, + {0x3341, 0x50, 0, 0}, {0x3342, 0x18, 0, 0}, {0x3343, 0x23, 0, 0}, + {0x3344, 0xad, 0, 0}, {0x3345, 0xd0, 0, 0}, {0x3346, 0xb8, 0, 0}, + {0x3347, 0xb4, 0, 0}, {0x3348, 0x04, 0, 0}, {0x3349, 0x98, 0, 0}, + {0x3355, 0x02, 0, 0}, {0x3358, 0x44, 0, 0}, {0x3359, 0x44, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x3367, 0x23, 0, 0}, {0x3368, 0xBB, 0, 0}, + {0x3369, 0xD6, 0, 0}, {0x336A, 0x2A, 0, 0}, {0x336B, 0x07, 0, 0}, + {0x336C, 0x00, 0, 0}, {0x336D, 0x23, 0, 0}, {0x336E, 0xC3, 0, 0}, + {0x336F, 0xDE, 0, 0}, {0x3370, 0x2b, 0, 0}, {0x3371, 0x07, 0, 0}, + {0x3372, 0x00, 0, 0}, {0x3373, 0x23, 0, 0}, {0x3374, 0x9e, 0, 0}, + {0x3375, 0xD6, 0, 0}, {0x3376, 0x29, 0, 0}, {0x3377, 0x07, 0, 0}, + {0x3378, 0x00, 0, 0}, {0x332a, 0x1d, 0, 0}, {0x331b, 0x08, 0, 0}, + {0x331c, 0x16, 0, 0}, {0x331d, 0x2d, 0, 0}, {0x331e, 0x54, 0, 0}, + {0x331f, 0x66, 0, 0}, {0x3320, 0x73, 0, 0}, {0x3321, 0x80, 0, 0}, + {0x3322, 0x8c, 0, 0}, {0x3323, 0x95, 0, 0}, {0x3324, 0x9d, 0, 0}, + {0x3325, 0xac, 0, 0}, {0x3326, 0xb8, 0, 0}, {0x3327, 0xcc, 0, 0}, + {0x3328, 0xdd, 0, 0}, {0x3329, 0xee, 0, 0}, {0x332e, 0x04, 0, 0}, + {0x332f, 0x04, 0, 0}, {0x3331, 0x02, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3610, 0x40, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x307C, 0x10, 0, 0}, {0x3012, 0x10, 0, 0}, {0x3023, 0x06, 0, 0}, + {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, {0x302a, 0x03, 0, 0}, + {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, {0x300d, 0x01, 0, 0}, + {0x30d7, 0x90, 0, 0}, {0x3069, 0x04, 0, 0}, {0x303e, 0x00, 0, 0}, + {0x303f, 0xc0, 0, 0}, {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, + {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, {0x3362, 0x34, 0, 0}, + {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, {0x308a, 0x03, 0, 0}, + {0x308b, 0x00, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, + {0x3361, 0x04, 0, 0}, {0x3362, 0x23, 0, 0}, {0x3363, 0x28, 0, 0}, + {0x3364, 0x5c, 0, 0}, {0x3403, 0x42, 0, 0}, {0x3088, 0x02, 0, 0}, + {0x3089, 0xD0, 0, 0}, {0x308a, 0x01, 0, 0}, {0x308b, 0xe0, 0, 0}, + {0x304c, 0x83, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0xA1, 0, 0}, + {0x3011, 0x00, 0, 0}, {0x3010, 0x81, 0, 0}, {0x3014, 0x84, 0, 0}, + {0x302e, 0x00, 0, 0}, {0x302d, 0x00, 0, 0}, {0x3071, 0xeb, 0, 0}, + {0x301C, 0x02, 0, 0}, {0x3404, 0x02, 0, 0}, +}; + +static struct reg_value ov3640_setting_30fps_PAL_720_576[] = { + {0x3012, 0x80, 0, 0}, {0x304d, 0x45, 0, 0}, {0x30a7, 0x5e, 0, 0}, + {0x3086, 0x16, 0, 0}, {0x309C, 0x1a, 0, 0}, {0x30a2, 0xe4, 0, 0}, + {0x30aa, 0x42, 0, 0}, {0x30b0, 0xff, 0, 0}, {0x30b1, 0xff, 0, 0}, + {0x30b2, 0x10, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x304c, 0x81, 0, 0}, {0x30d7, 0x10, 0, 0}, + {0x30d9, 0x0d, 0, 0}, {0x30db, 0x08, 0, 0}, {0x3016, 0x82, 0, 0}, + {0x3018, 0x48, 0, 0}, {0x3019, 0x40, 0, 0}, {0x301a, 0x82, 0, 0}, + {0x307d, 0x00, 0, 0}, {0x3087, 0x02, 0, 0}, {0x3082, 0x20, 0, 0}, + {0x3015, 0x12, 0, 0}, {0x3014, 0x84, 0, 0}, {0x3013, 0xf7, 0, 0}, + {0x303c, 0x08, 0, 0}, {0x303d, 0x18, 0, 0}, {0x303e, 0x06, 0, 0}, + {0x303F, 0x0c, 0, 0}, {0x3030, 0x62, 0, 0}, {0x3031, 0x26, 0, 0}, + {0x3032, 0xe6, 0, 0}, {0x3033, 0x6e, 0, 0}, {0x3034, 0xea, 0, 0}, + {0x3035, 0xae, 0, 0}, {0x3036, 0xa6, 0, 0}, {0x3037, 0x6a, 0, 0}, + {0x3104, 0x02, 0, 0}, {0x3105, 0xfd, 0, 0}, {0x3106, 0x00, 0, 0}, + {0x3107, 0xff, 0, 0}, {0x3300, 0x13, 0, 0}, {0x3301, 0xde, 0, 0}, + {0x3302, 0xcf, 0, 0}, {0x3312, 0x26, 0, 0}, {0x3314, 0x42, 0, 0}, + {0x3313, 0x2b, 0, 0}, {0x3315, 0x42, 0, 0}, {0x3310, 0xd0, 0, 0}, + {0x3311, 0xbd, 0, 0}, {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, + {0x330e, 0x56, 0, 0}, {0x330f, 0x5c, 0, 0}, {0x330b, 0x1c, 0, 0}, + {0x3306, 0x5c, 0, 0}, {0x3307, 0x11, 0, 0}, {0x336a, 0x52, 0, 0}, + {0x3370, 0x46, 0, 0}, {0x3376, 0x38, 0, 0}, {0x30b8, 0x20, 0, 0}, + {0x30b9, 0x17, 0, 0}, {0x30ba, 0x04, 0, 0}, {0x30bb, 0x08, 0, 0}, + {0x3507, 0x06, 0, 0}, {0x350a, 0x4f, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3600, 0xc0, 0, 0}, {0x3088, 0x08, 0, 0}, + {0x3089, 0x00, 0, 0}, {0x308a, 0x06, 0, 0}, {0x308b, 0x00, 0, 0}, + {0x308d, 0x04, 0, 0}, {0x3086, 0x03, 0, 0}, {0x3086, 0x00, 0, 0}, + {0x30a9, 0xb5, 0, 0}, {0x3317, 0x04, 0, 0}, {0x3316, 0xf8, 0, 0}, + {0x3312, 0x17, 0, 0}, {0x3314, 0x30, 0, 0}, {0x3313, 0x23, 0, 0}, + {0x3315, 0x3e, 0, 0}, {0x3311, 0x9e, 0, 0}, {0x3310, 0xc0, 0, 0}, + {0x330c, 0x18, 0, 0}, {0x330d, 0x18, 0, 0}, {0x330e, 0x5e, 0, 0}, + {0x330f, 0x6c, 0, 0}, {0x330b, 0x1c, 0, 0}, {0x3306, 0x5c, 0, 0}, + {0x3307, 0x11, 0, 0}, {0x3308, 0x25, 0, 0}, {0x3340, 0x20, 0, 0}, + {0x3341, 0x50, 0, 0}, {0x3342, 0x18, 0, 0}, {0x3343, 0x23, 0, 0}, + {0x3344, 0xad, 0, 0}, {0x3345, 0xd0, 0, 0}, {0x3346, 0xb8, 0, 0}, + {0x3347, 0xb4, 0, 0}, {0x3348, 0x04, 0, 0}, {0x3349, 0x98, 0, 0}, + {0x3355, 0x02, 0, 0}, {0x3358, 0x44, 0, 0}, {0x3359, 0x44, 0, 0}, + {0x3300, 0x13, 0, 0}, {0x3367, 0x23, 0, 0}, {0x3368, 0xBB, 0, 0}, + {0x3369, 0xD6, 0, 0}, {0x336A, 0x2A, 0, 0}, {0x336B, 0x07, 0, 0}, + {0x336C, 0x00, 0, 0}, {0x336D, 0x23, 0, 0}, {0x336E, 0xC3, 0, 0}, + {0x336F, 0xDE, 0, 0}, {0x3370, 0x2b, 0, 0}, {0x3371, 0x07, 0, 0}, + {0x3372, 0x00, 0, 0}, {0x3373, 0x23, 0, 0}, {0x3374, 0x9e, 0, 0}, + {0x3375, 0xD6, 0, 0}, {0x3376, 0x29, 0, 0}, {0x3377, 0x07, 0, 0}, + {0x3378, 0x00, 0, 0}, {0x332a, 0x1d, 0, 0}, {0x331b, 0x08, 0, 0}, + {0x331c, 0x16, 0, 0}, {0x331d, 0x2d, 0, 0}, {0x331e, 0x54, 0, 0}, + {0x331f, 0x66, 0, 0}, {0x3320, 0x73, 0, 0}, {0x3321, 0x80, 0, 0}, + {0x3322, 0x8c, 0, 0}, {0x3323, 0x95, 0, 0}, {0x3324, 0x9d, 0, 0}, + {0x3325, 0xac, 0, 0}, {0x3326, 0xb8, 0, 0}, {0x3327, 0xcc, 0, 0}, + {0x3328, 0xdd, 0, 0}, {0x3329, 0xee, 0, 0}, {0x332e, 0x04, 0, 0}, + {0x332f, 0x04, 0, 0}, {0x3331, 0x02, 0, 0}, {0x3100, 0x02, 0, 0}, + {0x3301, 0xde, 0, 0}, {0x3304, 0xfc, 0, 0}, {0x3400, 0x00, 0, 0}, + {0x3404, 0x00, 0, 0}, {0x3610, 0x40, 0, 0}, {0x304c, 0x81, 0, 0}, + {0x307C, 0x10, 0, 0}, {0x3012, 0x10, 0, 0}, {0x3023, 0x06, 0, 0}, + {0x3026, 0x03, 0, 0}, {0x3027, 0x04, 0, 0}, {0x302a, 0x03, 0, 0}, + {0x302b, 0x10, 0, 0}, {0x3075, 0x24, 0, 0}, {0x300d, 0x01, 0, 0}, + {0x30d7, 0x90, 0, 0}, {0x3069, 0x04, 0, 0}, {0x303e, 0x00, 0, 0}, + {0x303f, 0xc0, 0, 0}, {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, + {0x3360, 0x0c, 0, 0}, {0x3361, 0x04, 0, 0}, {0x3362, 0x34, 0, 0}, + {0x3363, 0x08, 0, 0}, {0x3364, 0x04, 0, 0}, {0x3403, 0x42, 0, 0}, + {0x3088, 0x04, 0, 0}, {0x3089, 0x00, 0, 0}, {0x308a, 0x03, 0, 0}, + {0x308b, 0x00, 0, 0}, {0x300e, 0x32, 0, 0}, {0x300f, 0x21, 0, 0}, + {0x3010, 0x20, 0, 0}, {0x3011, 0x01, 0, 0}, {0x304c, 0x82, 0, 0}, + {0x3302, 0xef, 0, 0}, {0x335f, 0x34, 0, 0}, {0x3360, 0x0c, 0, 0}, + {0x3361, 0x04, 0, 0}, {0x3362, 0x23, 0, 0}, {0x3363, 0x28, 0, 0}, + {0x3364, 0x5c, 0, 0}, {0x3403, 0x42, 0, 0}, {0x3088, 0x02, 0, 0}, + {0x3089, 0xD0, 0, 0}, {0x308a, 0x02, 0, 0}, {0x308b, 0x40, 0, 0}, + {0x304c, 0x83, 0, 0}, {0x300e, 0x39, 0, 0}, {0x300f, 0xA1, 0, 0}, + {0x3011, 0x00, 0, 0}, {0x3010, 0x81, 0, 0}, {0x3014, 0x84, 0, 0}, + {0x302e, 0x00, 0, 0}, {0x302d, 0x00, 0, 0}, {0x3071, 0xeb, 0, 0}, + {0x301C, 0x02, 0, 0}, {0x3404, 0x02, 0, 0}, +}; + +static struct ov3640_mode_info ov3640_mode_info_data[2][ov3640_mode_MAX + 1] = { + { + {ov3640_mode_VGA_640_480, 640, 480, + ov3640_setting_15fps_VGA_640_480, + ARRAY_SIZE(ov3640_setting_15fps_VGA_640_480)}, + {ov3640_mode_QVGA_320_240, 320, 240, + ov3640_setting_15fps_QVGA_320_240, + ARRAY_SIZE(ov3640_setting_15fps_QVGA_320_240)}, + {ov3640_mode_XGA_1024_768, 1024, 768, + ov3640_setting_15fps_XGA_1024_768, + ARRAY_SIZE(ov3640_setting_15fps_XGA_1024_768)}, + {ov3640_mode_QXGA_2048_1536, 2048, 1536, + ov3640_setting_15fps_QXGA_2048_1536, + ARRAY_SIZE(ov3640_setting_15fps_QXGA_2048_1536)}, + {ov3640_mode_NTSC_720_480, 720, 480, + ov3640_setting_15fps_NTSC_720_480, + ARRAY_SIZE(ov3640_setting_15fps_NTSC_720_480)}, + {ov3640_mode_PAL_720_576, 720, 576, + ov3640_setting_15fps_PAL_720_576, + ARRAY_SIZE(ov3640_setting_15fps_PAL_720_576)}, + }, + { + {ov3640_mode_VGA_640_480, 640, 480, + ov3640_setting_30fps_VGA_640_480, + ARRAY_SIZE(ov3640_setting_30fps_VGA_640_480)}, + {ov3640_mode_QVGA_320_240, 320, 240, + ov3640_setting_30fps_QVGA_320_240, + ARRAY_SIZE(ov3640_setting_30fps_QVGA_320_240)}, + {ov3640_mode_XGA_1024_768, 1024, 768, + ov3640_setting_30fps_XGA_1024_768, + ARRAY_SIZE(ov3640_setting_30fps_XGA_1024_768)}, + {ov3640_mode_QXGA_2048_1536, 0, 0, NULL, 0}, + {ov3640_mode_NTSC_720_480, 720, 480, + ov3640_setting_30fps_NTSC_720_480, + ARRAY_SIZE(ov3640_setting_30fps_NTSC_720_480)}, + {ov3640_mode_PAL_720_576, 720, 576, + ov3640_setting_30fps_PAL_720_576, + ARRAY_SIZE(ov3640_setting_30fps_PAL_720_576)}, + }, +}; + +static struct regulator *io_regulator; +static struct regulator *core_regulator; +static struct regulator *analog_regulator; +static struct regulator *gpo_regulator; +static struct mxc_camera_platform_data *camera_plat; + +static int ov3640_probe(struct i2c_client *adapter, + const struct i2c_device_id *device_id); +static int ov3640_remove(struct i2c_client *client); + +static s32 ov3640_read_reg(u16 reg, u8 *val); +static s32 ov3640_write_reg(u16 reg, u8 val); + +static const struct i2c_device_id ov3640_id[] = { + {"ov3640", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ov3640_id); + +static struct i2c_driver ov3640_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ov3640", + }, + .probe = ov3640_probe, + .remove = ov3640_remove, + .id_table = ov3640_id, +}; + +extern void gpio_sensor_active(unsigned int csi_index); +extern void gpio_sensor_inactive(unsigned int csi); + +static s32 ov3640_write_reg(u16 reg, u8 val) +{ + u8 au8Buf[3] = {0}; + + au8Buf[0] = reg >> 8; + au8Buf[1] = reg & 0xff; + au8Buf[2] = val; + + if (i2c_master_send(ov3640_data.i2c_client, au8Buf, 3) < 0) { + pr_err("%s:write reg error:reg=%x,val=%x\n", + __func__, reg, val); + return -1; + } + + return 0; +} + +static s32 ov3640_read_reg(u16 reg, u8 *val) +{ + u8 au8RegBuf[2] = {0}; + u8 u8RdVal = 0; + + au8RegBuf[0] = reg >> 8; + au8RegBuf[1] = reg & 0xff; + + if (2 != i2c_master_send(ov3640_data.i2c_client, au8RegBuf, 2)) { + pr_err("%s:write reg error:reg=%x\n", + __func__, reg); + return -1; + } + + if (1 != i2c_master_recv(ov3640_data.i2c_client, &u8RdVal, 1)) { + pr_err("%s:read reg error:reg=%x,val=%x\n", + __func__, reg, u8RdVal); + return -1; + } + + *val = u8RdVal; + + return u8RdVal; +} + +static int ov3640_init_mode(enum ov3640_frame_rate frame_rate, + enum ov3640_mode mode) +{ + struct reg_value *pModeSetting = NULL; + s32 i = 0; + s32 iModeSettingArySize = 0; + register u32 Delay_ms = 0; + register u16 RegAddr = 0; + register u8 Mask = 0; + register u8 Val = 0; + u8 RegVal = 0; + int retval = 0; + + if (mode > ov3640_mode_MAX || mode < ov3640_mode_MIN) { + pr_err("Wrong ov3640 mode detected!\n"); + return -1; + } + + pModeSetting = ov3640_mode_info_data[frame_rate][mode].init_data_ptr; + iModeSettingArySize = + ov3640_mode_info_data[frame_rate][mode].init_data_size; + + ov3640_data.pix.width = ov3640_mode_info_data[frame_rate][mode].width; + ov3640_data.pix.height = ov3640_mode_info_data[frame_rate][mode].height; + + for (i = 0; i < iModeSettingArySize; ++i, ++pModeSetting) { + Delay_ms = pModeSetting->u32Delay_ms; + RegAddr = pModeSetting->u16RegAddr; + Val = pModeSetting->u8Val; + Mask = pModeSetting->u8Mask; + + if (Mask) { + retval = ov3640_read_reg(RegAddr, &RegVal); + if (retval < 0) + goto err; + + RegVal &= ~(u8)Mask; + Val &= Mask; + Val |= RegVal; + } + + retval = ov3640_write_reg(RegAddr, Val); + if (retval < 0) + goto err; + + if (Delay_ms) + msleep(Delay_ms); + } +err: + return retval; +} + +/* --------------- IOCTL functions from v4l2_int_ioctl_desc --------------- */ + +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + if (s == NULL) { + pr_err(" ERROR!! no slave device set!\n"); + return -1; + } + + memset(p, 0, sizeof(*p)); + p->u.bt656.clock_curr = ov3640_data.mclk; + pr_debug(" clock_curr=mclk=%d\n", ov3640_data.mclk); + p->if_type = V4L2_IF_TYPE_BT656; + p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; + p->u.bt656.clock_min = OV3640_XCLK_MIN; + p->u.bt656.clock_max = OV3640_XCLK_MAX; + p->u.bt656.bt_sync_correct = 1; /* Indicate external vsync */ + + return 0; +} + +/*! + * ioctl_s_power - V4L2 sensor interface handler for VIDIOC_S_POWER ioctl + * @s: pointer to standard V4L2 device structure + * @on: indicates power mode (on or off) + * + * Turns the power on or off, depending on the value of on and returns the + * appropriate error code. + */ +static int ioctl_s_power(struct v4l2_int_device *s, int on) +{ + struct sensor *sensor = s->priv; + + if (on && !sensor->on) { + gpio_sensor_active(ov3640_data.csi); + if (io_regulator) + if (regulator_enable(io_regulator) != 0) + return -EIO; + if (core_regulator) + if (regulator_enable(core_regulator) != 0) + return -EIO; + if (gpo_regulator) + if (regulator_enable(gpo_regulator) != 0) + return -EIO; + if (analog_regulator) + if (regulator_enable(analog_regulator) != 0) + return -EIO; + /* Make sure power on */ + if (camera_plat->pwdn) + camera_plat->pwdn(0); + + } else if (!on && sensor->on) { + if (analog_regulator) + regulator_disable(analog_regulator); + if (core_regulator) + regulator_disable(core_regulator); + if (io_regulator) + regulator_disable(io_regulator); + if (gpo_regulator) + regulator_disable(gpo_regulator); + gpio_sensor_inactive(ov3640_data.csi); + } + + sensor->on = on; + + return 0; +} + +/*! + * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the sensor's video CAPTURE parameters. + */ +static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_captureparm *cparm = &a->parm.capture; + int ret = 0; + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + cparm->capability = sensor->streamcap.capability; + cparm->timeperframe = sensor->streamcap.timeperframe; + cparm->capturemode = sensor->streamcap.capturemode; + ret = 0; + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + ret = -EINVAL; + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the sensor to use the input parameters, if possible. If + * not possible, reverts to the old parameters and returns the + * appropriate error code. + */ +static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct sensor *sensor = s->priv; + struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe; + u32 tgt_fps; /* target frames per secound */ + enum ov3640_frame_rate frame_rate; + int ret = 0; + + /* Make sure power on */ + if (camera_plat->pwdn) + camera_plat->pwdn(0); + + switch (a->type) { + /* This is the only case currently handled. */ + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + /* Check that the new frame rate is allowed. */ + if ((timeperframe->numerator == 0) || + (timeperframe->denominator == 0)) { + timeperframe->denominator = DEFAULT_FPS; + timeperframe->numerator = 1; + } + + tgt_fps = timeperframe->denominator / + timeperframe->numerator; + + if (tgt_fps > MAX_FPS) { + timeperframe->denominator = MAX_FPS; + timeperframe->numerator = 1; + } else if (tgt_fps < MIN_FPS) { + timeperframe->denominator = MIN_FPS; + timeperframe->numerator = 1; + } + + /* Actual frame rate we use */ + tgt_fps = timeperframe->denominator / + timeperframe->numerator; + + if (tgt_fps == 15) + frame_rate = ov3640_15_fps; + else if (tgt_fps == 30) + frame_rate = ov3640_30_fps; + else { + pr_err(" The camera frame rate is not supported!\n"); + return -EINVAL; + } + + sensor->streamcap.timeperframe = *timeperframe; + sensor->streamcap.capturemode = + (u32)a->parm.capture.capturemode; + + ret = ov3640_init_mode(frame_rate, + sensor->streamcap.capturemode); + break; + + /* These are all the possible cases. */ + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + case V4L2_BUF_TYPE_VIDEO_OVERLAY: + case V4L2_BUF_TYPE_VBI_CAPTURE: + case V4L2_BUF_TYPE_VBI_OUTPUT: + case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: + case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: + pr_debug(" type is not " \ + "V4L2_BUF_TYPE_VIDEO_CAPTURE but %d\n", + a->type); + ret = -EINVAL; + break; + + default: + pr_debug(" type is unknown - %d\n", a->type); + ret = -EINVAL; + break; + } + + return ret; +} + +/*! + * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the sensor's current pixel format in the v4l2_format + * parameter. + */ +static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct sensor *sensor = s->priv; + + f->fmt.pix = sensor->pix; + + return 0; +} + +/*! + * ioctl_g_ctrl - V4L2 sensor interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_G_CTRL ioctl structure + * + * If the requested control is supported, returns the control's current + * value from the video_control[] array. Otherwise, returns -EINVAL + * if the control is not supported. + */ +static int ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int ret = 0; + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + vc->value = ov3640_data.brightness; + break; + case V4L2_CID_HUE: + vc->value = ov3640_data.hue; + break; + case V4L2_CID_CONTRAST: + vc->value = ov3640_data.contrast; + break; + case V4L2_CID_SATURATION: + vc->value = ov3640_data.saturation; + break; + case V4L2_CID_RED_BALANCE: + vc->value = ov3640_data.red; + break; + case V4L2_CID_BLUE_BALANCE: + vc->value = ov3640_data.blue; + break; + case V4L2_CID_EXPOSURE: + vc->value = ov3640_data.ae_mode; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/*! + * ioctl_s_ctrl - V4L2 sensor interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @vc: standard V4L2 VIDIOC_S_CTRL ioctl structure + * + * If the requested control is supported, sets the control's current + * value in HW (and updates the video_control[] array). Otherwise, + * returns -EINVAL if the control is not supported. + */ +static int ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *vc) +{ + int retval = 0; + + pr_debug("In ov3640:ioctl_s_ctrl %d\n", + vc->id); + + switch (vc->id) { + case V4L2_CID_BRIGHTNESS: + break; + case V4L2_CID_CONTRAST: + break; + case V4L2_CID_SATURATION: + break; + case V4L2_CID_HUE: + break; + case V4L2_CID_AUTO_WHITE_BALANCE: + break; + case V4L2_CID_DO_WHITE_BALANCE: + break; + case V4L2_CID_RED_BALANCE: + break; + case V4L2_CID_BLUE_BALANCE: + break; + case V4L2_CID_GAMMA: + break; + case V4L2_CID_EXPOSURE: + break; + case V4L2_CID_AUTOGAIN: + break; + case V4L2_CID_GAIN: + break; + case V4L2_CID_HFLIP: + break; + case V4L2_CID_VFLIP: + break; + default: + retval = -EPERM; + break; + } + + return retval; +} + +/*! + * ioctl_init - V4L2 sensor interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + + return 0; +} + +/*! + * ioctl_dev_init - V4L2 sensor interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + struct sensor *sensor = s->priv; + u32 tgt_xclk; /* target xclk */ + u32 tgt_fps; /* target frames per secound */ + enum ov3640_frame_rate frame_rate; + + gpio_sensor_active(ov3640_data.csi); + ov3640_data.on = true; + + /* mclk */ + tgt_xclk = ov3640_data.mclk; + tgt_xclk = min(tgt_xclk, (u32)OV3640_XCLK_MAX); + tgt_xclk = max(tgt_xclk, (u32)OV3640_XCLK_MIN); + ov3640_data.mclk = tgt_xclk; + + pr_debug(" Setting mclk to %d MHz\n", tgt_xclk / 1000000); + set_mclk_rate(&ov3640_data.mclk, ov3640_data.csi); + + /* Default camera frame rate is set in probe */ + tgt_fps = sensor->streamcap.timeperframe.denominator / + sensor->streamcap.timeperframe.numerator; + + if (tgt_fps == 15) + frame_rate = ov3640_15_fps; + else if (tgt_fps == 30) + frame_rate = ov3640_30_fps; + else + return -EINVAL; /* Only support 15fps or 30fps now. */ + + return ov3640_init_mode(frame_rate, + sensor->streamcap.capturemode); +} + +/*! + * ioctl_dev_exit - V4L2 sensor interface handler for vidioc_int_dev_exit_num + * @s: pointer to standard V4L2 device structure + * + * Delinitialise the device when slave detaches to the master. + */ +static int ioctl_dev_exit(struct v4l2_int_device *s) +{ + gpio_sensor_inactive(ov3640_data.csi); + + return 0; +} + +/*! + * This structure defines all the ioctls for this module and links them to the + * enumeration. + */ +static struct v4l2_int_ioctl_desc ov3640_ioctl_desc[] = { + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func *)ioctl_dev_init}, + {vidioc_int_dev_exit_num, ioctl_dev_exit}, + {vidioc_int_s_power_num, (v4l2_int_ioctl_func *)ioctl_s_power}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func *)ioctl_g_ifparm}, +/* {vidioc_int_g_needs_reset_num, + (v4l2_int_ioctl_func *)ioctl_g_needs_reset}, */ +/* {vidioc_int_reset_num, (v4l2_int_ioctl_func *)ioctl_reset}, */ + {vidioc_int_init_num, (v4l2_int_ioctl_func *)ioctl_init}, +/* {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap}, */ +/* {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *)ioctl_try_fmt_cap}, */ + {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_g_fmt_cap}, +/* {vidioc_int_s_fmt_cap_num, (v4l2_int_ioctl_func *)ioctl_s_fmt_cap}, */ + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *)ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *)ioctl_s_parm}, +/* {vidioc_int_queryctrl_num, (v4l2_int_ioctl_func *)ioctl_queryctrl}, */ + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *)ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *)ioctl_s_ctrl}, +}; + +static struct v4l2_int_slave ov3640_slave = { + .ioctls = ov3640_ioctl_desc, + .num_ioctls = ARRAY_SIZE(ov3640_ioctl_desc), +}; + +static struct v4l2_int_device ov3640_int_device = { + .module = THIS_MODULE, + .name = "ov3640", + .type = v4l2_int_type_slave, + .u = { + .slave = &ov3640_slave, + }, +}; + +/*! + * ov3640 I2C probe function + * + * @param adapter struct i2c_adapter * + * @return Error code indicating success or failure + */ +static int ov3640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + struct mxc_camera_platform_data *plat_data = client->dev.platform_data; + + /* Set initial values for the sensor struct. */ + memset(&ov3640_data, 0, sizeof(ov3640_data)); + ov3640_data.mclk = 24000000; /* 6 - 54 MHz, typical 24MHz */ + ov3640_data.mclk = plat_data->mclk; + ov3640_data.csi = plat_data->csi; + + ov3640_data.i2c_client = client; + ov3640_data.pix.pixelformat = V4L2_PIX_FMT_UYVY; + ov3640_data.pix.width = 640; + ov3640_data.pix.height = 480; + ov3640_data.streamcap.capability = V4L2_MODE_HIGHQUALITY | + V4L2_CAP_TIMEPERFRAME; + ov3640_data.streamcap.capturemode = 0; + ov3640_data.streamcap.timeperframe.denominator = DEFAULT_FPS; + ov3640_data.streamcap.timeperframe.numerator = 1; + + if (plat_data->io_regulator) { + io_regulator = regulator_get(&client->dev, + plat_data->io_regulator); + if (!IS_ERR(io_regulator)) { + regulator_set_voltage(io_regulator, + OV3640_VOLTAGE_DIGITAL_IO, + OV3640_VOLTAGE_DIGITAL_IO); + if (regulator_enable(io_regulator) != 0) { + pr_err("%s:io set voltage error\n", __func__); + goto err1; + } else { + dev_dbg(&client->dev, + "%s:io set voltage ok\n", __func__); + } + } else + io_regulator = NULL; + } + + if (plat_data->core_regulator) { + core_regulator = regulator_get(&client->dev, + plat_data->core_regulator); + if (!IS_ERR(core_regulator)) { + regulator_set_voltage(core_regulator, + OV3640_VOLTAGE_DIGITAL_CORE, + OV3640_VOLTAGE_DIGITAL_CORE); + if (regulator_enable(core_regulator) != 0) { + pr_err("%s:core set voltage error\n", __func__); + goto err2; + } else { + dev_dbg(&client->dev, + "%s:core set voltage ok\n", __func__); + } + } else + core_regulator = NULL; + } + + if (plat_data->analog_regulator) { + analog_regulator = regulator_get(&client->dev, + plat_data->analog_regulator); + if (!IS_ERR(analog_regulator)) { + regulator_set_voltage(analog_regulator, + OV3640_VOLTAGE_ANALOG, + OV3640_VOLTAGE_ANALOG); + if (regulator_enable(analog_regulator) != 0) { + pr_err("%s:analog set voltage error\n", + __func__); + goto err3; + } else { + dev_dbg(&client->dev, + "%s:analog set voltage ok\n", __func__); + } + } else + analog_regulator = NULL; + } + + if (plat_data->gpo_regulator) { + gpo_regulator = regulator_get(&client->dev, + plat_data->gpo_regulator); + if (!IS_ERR(gpo_regulator)) { + regulator_set_voltage(gpo_regulator, + OV3640_VOLTAGE_DIGITAL_GPO, + OV3640_VOLTAGE_DIGITAL_GPO); + if (regulator_enable(gpo_regulator) != 0) { + pr_err("%s:gpo enable error\n", __func__); + goto err4; + } else { + dev_dbg(&client->dev, + "%s:gpo enable ok\n", __func__); + } + } else + gpo_regulator = NULL; + } + + if (plat_data->pwdn) + plat_data->pwdn(0); + + camera_plat = plat_data; + + ov3640_int_device.priv = &ov3640_data; + retval = v4l2_int_device_register(&ov3640_int_device); + + return retval; + +err4: + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } +err3: + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } +err2: + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } +err1: + return -1; +} + +/*! + * ov3640 I2C detach function + * + * @param client struct i2c_client * + * @return Error code indicating success or failure + */ +static int ov3640_remove(struct i2c_client *client) +{ + v4l2_int_device_unregister(&ov3640_int_device); + + if (gpo_regulator) { + regulator_disable(gpo_regulator); + regulator_put(gpo_regulator); + } + + if (analog_regulator) { + regulator_disable(analog_regulator); + regulator_put(analog_regulator); + } + + if (core_regulator) { + regulator_disable(core_regulator); + regulator_put(core_regulator); + } + + if (io_regulator) { + regulator_disable(io_regulator); + regulator_put(io_regulator); + } + + return 0; +} + +/*! + * ov3640 init function + * Called by insmod ov3640_camera.ko. + * + * @return Error code indicating success or failure + */ +static __init int ov3640_init(void) +{ + u8 err; + + err = i2c_add_driver(&ov3640_i2c_driver); + if (err != 0) + pr_err("%s:driver registration failed, error=%d \n", + __func__, err); + + return err; +} + +/*! + * OV3640 cleanup function + * Called on rmmod ov3640_camera.ko + * + * @return Error code indicating success or failure + */ +static void __exit ov3640_clean(void) +{ + i2c_del_driver(&ov3640_i2c_driver); +} + +module_init(ov3640_init); +module_exit(ov3640_clean); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OV3640 Camera Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("CSI"); diff --git a/drivers/media/video/mxc/capture/sensor_clock.c b/drivers/media/video/mxc/capture/sensor_clock.c new file mode 100644 index 000000000000..3d1bf8d71019 --- /dev/null +++ b/drivers/media/video/mxc/capture/sensor_clock.c @@ -0,0 +1,97 @@ +/* + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file sensor_clock.c + * + * @brief camera clock function + * + * @ingroup Camera + */ +#include <linux/init.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <mach/hardware.h> + +#if defined(CONFIG_MXC_IPU_V1) || defined(CONFIG_VIDEO_MXC_EMMA_CAMERA) \ + || defined(CONFIG_VIDEO_MXC_CSI_CAMERA_MODULE) \ + || defined(CONFIG_VIDEO_MXC_CSI_CAMERA) +/* + * set_mclk_rate + * + * @param p_mclk_freq mclk frequence + * + */ +void set_mclk_rate(uint32_t * p_mclk_freq) +{ + struct clk *clk; + uint32_t freq = 0; + + clk = clk_get(NULL, "csi_clk"); + + freq = clk_round_rate(clk, *p_mclk_freq); + clk_set_rate(clk, freq); + + *p_mclk_freq = freq; + + clk_put(clk); + pr_debug("mclk frequency = %d\n", *p_mclk_freq); +} +#else +/* + * set_mclk_rate + * + * @param p_mclk_freq mclk frequence + * @param csi csi 0 or csi 1 + * + */ +void set_mclk_rate(uint32_t *p_mclk_freq, uint32_t csi) +{ + struct clk *clk; + uint32_t freq = 0; + char *mclk; + + if (cpu_is_mx53()) { + if (csi == 0) + mclk = "ssi_ext1_clk"; + else { + pr_err("invalid csi num %d\n", csi); + return; + } + } else { + if (csi == 0) { + mclk = "csi_mclk1"; + } else if (csi == 1) { + mclk = "csi_mclk2"; + } else { + pr_err("invalid csi num %d\n", csi); + return; + } + } + + clk = clk_get(NULL, mclk); + + freq = clk_round_rate(clk, *p_mclk_freq); + clk_set_rate(clk, freq); + + *p_mclk_freq = freq; + + clk_put(clk); + pr_debug("%s frequency = %d\n", mclk, *p_mclk_freq); +} +#endif + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(set_mclk_rate); diff --git a/drivers/media/video/mxc/opl/Makefile b/drivers/media/video/mxc/opl/Makefile new file mode 100644 index 000000000000..092a62c5ac4a --- /dev/null +++ b/drivers/media/video/mxc/opl/Makefile @@ -0,0 +1,5 @@ +opl-objs := opl_mod.o rotate90_u16.o rotate270_u16.o \ + rotate90_u16_qcif.o rotate270_u16_qcif.o \ + vmirror_u16.o hmirror_rotate180_u16.o + +obj-$(CONFIG_VIDEO_MXC_OPL) += opl.o diff --git a/drivers/media/video/mxc/opl/hmirror_rotate180_u16.c b/drivers/media/video/mxc/opl/hmirror_rotate180_u16.c new file mode 100644 index 000000000000..3119a128c1f2 --- /dev/null +++ b/drivers/media/video/mxc/opl/hmirror_rotate180_u16.c @@ -0,0 +1,259 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include "opl.h" + +static inline u32 rot_left_u16(u16 x, unsigned int n) +{ + return (x << n) | (x >> (16 - n)); +} + +static inline u32 rot_left_u32(u32 x, unsigned int n) +{ + return (x << n) | (x >> (32 - n)); +} + +static inline u32 byte_swap_u32(u32 x) +{ + u32 t1, t2, t3; + + t1 = x ^ ((x << 16) | x >> 16); + t2 = t1 & 0xff00ffff; + t3 = (x >> 8) | (x << 24); + return t3 ^ (t2 >> 8); +} + +static int opl_hmirror_u16_by1(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_hmirror_u16_by2(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_hmirror_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_hmirror_u16_by8(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); + +int opl_hmirror_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride) +{ + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + if (width % 8 == 0) + return opl_hmirror_u16_by8(src, src_line_stride, width, height, + dst, dst_line_stride, 0); + else if (width % 4 == 0) + return opl_hmirror_u16_by4(src, src_line_stride, width, height, + dst, dst_line_stride, 0); + else if (width % 2 == 0) + return opl_hmirror_u16_by2(src, src_line_stride, width, height, + dst, dst_line_stride, 0); + else /* (width % 1) */ + return opl_hmirror_u16_by1(src, src_line_stride, width, height, + dst, dst_line_stride, 0); +} + +int opl_rotate180_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride) +{ + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + if (width % 8 == 0) + return opl_hmirror_u16_by8(src, src_line_stride, width, height, + dst, dst_line_stride, 1); + else if (width % 4 == 0) + return opl_hmirror_u16_by4(src, src_line_stride, width, height, + dst, dst_line_stride, 1); + else if (width % 2 == 0) + return opl_hmirror_u16_by2(src, src_line_stride, width, height, + dst, dst_line_stride, 1); + else /* (width % 1) */ + return opl_hmirror_u16_by1(src, src_line_stride, width, height, + dst, dst_line_stride, 1); +} + +static int opl_hmirror_u16_by1(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + u16 pixel; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 1) * BYTES_PER_PIXEL + - (BYTES_PER_PIXEL - BYTES_PER_PIXEL); + for (j = 0; j < width; j++) { + pixel = *(u16 *) psrc; + *(u16 *) pdst = pixel; + psrc += BYTES_PER_PIXEL; + pdst -= BYTES_PER_PIXEL; + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +static int opl_hmirror_u16_by2(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + u32 pixelsin, pixelsout; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 2) * BYTES_PER_PIXEL; + for (j = 0; j < (width >> 1); j++) { + pixelsin = *(u32 *) psrc; + pixelsout = rot_left_u32(pixelsin, 16); + *(u32 *) pdst = pixelsout; + psrc += BYTES_PER_2PIXEL; + pdst -= BYTES_PER_2PIXEL; + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +static int opl_hmirror_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + + union doubleword { + u64 dw; + u32 w[2]; + }; + + union doubleword inbuf; + union doubleword outbuf; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 4) * BYTES_PER_PIXEL; + for (j = 0; j < (width >> 2); j++) { + inbuf.dw = *(u64 *) psrc; + outbuf.w[0] = rot_left_u32(inbuf.w[1], 16); + outbuf.w[1] = rot_left_u32(inbuf.w[0], 16); + *(u64 *) pdst = outbuf.dw; + psrc += BYTES_PER_4PIXEL; + pdst -= BYTES_PER_4PIXEL; + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + return OPLERR_SUCCESS; +} + +static int opl_hmirror_u16_by8(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const u8 *src_row_addr; + const u8 *psrc; + u8 *dst_row_addr, *pdst; + int i, j; + + src_row_addr = src; + if (vmirror) { + dst_row_addr = dst + dst_line_stride * (height - 1); + dst_line_stride = -dst_line_stride; + } else + dst_row_addr = dst; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* Loop over each pixel */ + psrc = src_row_addr; + pdst = dst_row_addr + (width - 1) * BYTES_PER_PIXEL - 2; + for (j = (width >> 3); j > 0; j--) { + __asm__ volatile ( + "ldmia %0!,{r2-r5}\n\t" + "mov r6, r2\n\t" + "mov r7, r3\n\t" + "mov r2, r5, ROR #16\n\t" + "mov r3, r4, ROR #16\n\t" + "mov r4, r7, ROR #16\n\t" + "mov r5, r6, ROR #16\n\t" + "stmda %1!,{r2-r5}\n\t" + + :"+r"(psrc), "+r"(pdst) + :"0"(psrc), "1"(pdst) + :"r2", "r3", "r4", "r5", "r6", "r7", + "memory" + ); + } + src_row_addr += src_line_stride; + dst_row_addr += dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_hmirror_u16); +EXPORT_SYMBOL(opl_rotate180_u16); diff --git a/drivers/media/video/mxc/opl/opl.h b/drivers/media/video/mxc/opl/opl.h new file mode 100644 index 000000000000..24644c8e78fa --- /dev/null +++ b/drivers/media/video/mxc/opl/opl.h @@ -0,0 +1,162 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup OPLIP OPL Image Processing + */ +/*! + * @file opl.h + * + * @brief The OPL (Open Primitives Library) Image Processing library defines + * efficient functions for rotation and mirroring. + * + * It includes ARM9-optimized rotation and mirroring functions. It is derived + * from the original OPL project which is found at sourceforge.freescale.net. + * + * @ingroup OPLIP + */ +#ifndef __OPL_H__ +#define __OPL_H__ + +#include <linux/types.h> + +#define BYTES_PER_PIXEL 2 +#define CACHE_LINE_WORDS 8 +#define BYTES_PER_WORD 4 + +#define BYTES_PER_2PIXEL (BYTES_PER_PIXEL * 2) +#define BYTES_PER_4PIXEL (BYTES_PER_PIXEL * 4) +#define BYTES_PER_8PIXEL (BYTES_PER_PIXEL * 8) + +#define QCIF_Y_WIDTH 176 +#define QCIF_Y_HEIGHT 144 + +/*! Enumerations of opl error code */ +enum opl_error { + OPLERR_SUCCESS = 0, + OPLERR_NULL_PTR, + OPLERR_BAD_ARG, + OPLERR_DIV_BY_ZERO, + OPLERR_OVER_FLOW, + OPLERR_UNDER_FLOW, + OPLERR_MISALIGNED, +}; + +/*! + * @brief Rotate a 16bbp buffer 90 degrees clockwise. + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate90_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 180 degrees clockwise. + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate180_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 270 degrees clockwise + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate270_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride); + +/*! + * @brief Mirror a 16bpp buffer horizontally + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_hmirror_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride); + +/*! + * @brief Mirror a 16bpp buffer vertically + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_vmirror_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 90 degrees clockwise and mirror vertically + * It is equivalent to rotate 270 degree and mirror horizontally + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate90_vmirror_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride); + +/*! + * @brief Rotate a 16bbp buffer 270 degrees clockwise and mirror vertically + * It is equivalent to rotate 90 degree and mirror horizontally + * + * @param src Pointer to the input buffer + * @param src_line_stride Length in bytes of a raster line of the input buffer + * @param width Width in pixels of the region in the input buffer + * @param height Height in pixels of the region in the input buffer + * @param dst Pointer to the output buffer + * @param dst_line_stride Length in bytes of a raster line of the output buffer + * + * @return Standard OPL error code. See enumeration for possible result codes. + */ +int opl_rotate270_vmirror_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride); + +#endif /* __OPL_H__ */ diff --git a/drivers/media/video/mxc/opl/opl_mod.c b/drivers/media/video/mxc/opl/opl_mod.c new file mode 100644 index 000000000000..a581aadda252 --- /dev/null +++ b/drivers/media/video/mxc/opl/opl_mod.c @@ -0,0 +1,30 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> + +static __init int opl_init(void) +{ + return 0; +} + +static void __exit opl_exit(void) +{ +} + +module_init(opl_init); +module_exit(opl_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("OPL Software Rotation/Mirroring"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxc/opl/rotate270_u16.c b/drivers/media/video/mxc/opl/rotate270_u16.c new file mode 100644 index 000000000000..add87f1a9e44 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate270_u16.c @@ -0,0 +1,285 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include "opl.h" + +static int opl_rotate270_u16_by16(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror); +static int opl_rotate270_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_rotate270_vmirror_u16_both(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror); +int opl_rotate270_u16_qcif(const u8 * src, u8 * dst); + +int opl_rotate270_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride) +{ + return opl_rotate270_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 0); +} + +int opl_rotate270_vmirror_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride) +{ + return opl_rotate270_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 1); +} + +static int opl_rotate270_vmirror_u16_both(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int BLOCK_SIZE_PIXELS_BY4 = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + /* The QCIF algorithm doesn't support vertical mirroring */ + if (vmirror == 0 && width == QCIF_Y_WIDTH && height == QCIF_Y_HEIGHT + && src_line_stride == QCIF_Y_WIDTH * 2 + && src_line_stride == QCIF_Y_HEIGHT * 2) + return opl_rotate270_u16_qcif(src, dst); + else if (width % BLOCK_SIZE_PIXELS == 0 + && height % BLOCK_SIZE_PIXELS == 0) + return opl_rotate270_u16_by16(src, src_line_stride, width, + height, dst, dst_line_stride, + vmirror); + else if (width % BLOCK_SIZE_PIXELS_BY4 == 0 + && height % BLOCK_SIZE_PIXELS_BY4 == 0) + return opl_rotate270_u16_by4(src, src_line_stride, width, + height, dst, dst_line_stride, + vmirror); + else + return OPLERR_BAD_ARG; +} + +/* + * Rotate Counter Clockwise, divide RGB component into 16 row strips, read + * non sequentially and write sequentially. This is done in 16 line strips + * so that the cache is used better. Cachelines are 8 words = 32 bytes. Pixels + * are 2 bytes. The 16 reads will be cache misses, but the next 240 should + * be from cache. The writes to the output buffer will be sequential for 16 + * writes. + * + * Example: + * Input data matrix: output matrix + * + * 0 | 1 | 2 | 3 | 4 | 4 | 0 | 0 | 3 | + * 4 | 3 | 2 | 1 | 0 | 3 | 1 | 9 | 6 | + * 6 | 7 | 8 | 9 | 0 | 2 | 2 | 8 | 2 | + * 5 | 3 | 2 | 6 | 3 | 1 | 3 | 7 | 3 | + * ^ 0 | 4 | 6 | 5 | < Write the input data sequentially + * Read first column + * Start at the bottom + * Move to next column and repeat + * + * Loop over k decreasing (blocks) + * in_block_ptr = src + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_PIXELS) * (RGB_WIDTH_BYTES) + * out_block_ptr = dst + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_BYTES) + (RGB_WIDTH_PIXELS - 1) + * * RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + * + * Loop over i decreasing (width) + * Each pix: + * in_block_ptr += RGB_WIDTH_BYTES + * out_block_ptr += 4 + * + * Each row of block: + * in_block_ptr -= RGB_WIDTH_BYTES * BLOCK_SIZE_PIXELS - 2 + * out_block_ptr -= RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + 2 * BLOCK_SIZE_PIXELS; + * + * It may perform vertical mirroring too depending on the vmirror flag. + */ +static int opl_rotate270_u16_by16(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + - BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS + : dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS) * src_line_stride; + out_block_ptr = dst + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS * BYTES_PER_PIXEL) + + (width - 1) * dst_line_stride; + + /* + * For vertical mirroring the writing starts from the + * first line + */ + if (vmirror) + out_block_ptr -= dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + :"+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + :"0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + :"r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr -= IN_INDEX; + out_block_ptr -= OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +/* + * Rotate Counter Clockwise, divide RGB component into 4 row strips, read + * non sequentially and write sequentially. This is done in 4 line strips + * so that the cache is used better. Cachelines are 8 words = 32 bytes. Pixels + * are 2 bytes. The 4 reads will be cache misses, but the next 60 should + * be from cache. The writes to the output buffer will be sequential for 4 + * writes. + * + * Example: + * Input data matrix: output matrix + * + * 0 | 1 | 2 | 3 | 4 | 4 | 0 | 0 | 3 | + * 4 | 3 | 2 | 1 | 0 | 3 | 1 | 9 | 6 | + * 6 | 7 | 8 | 9 | 0 | 2 | 2 | 8 | 2 | + * 5 | 3 | 2 | 6 | 3 | 1 | 3 | 7 | 3 | + * ^ 0 | 4 | 6 | 5 | < Write the input data sequentially + * Read first column + * Start at the bottom + * Move to next column and repeat + * + * Loop over k decreasing (blocks) + * in_block_ptr = src + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_PIXELS) * (RGB_WIDTH_BYTES) + * out_block_ptr = dst + (((RGB_HEIGHT_PIXELS / BLOCK_SIZE_PIXELS) - k) + * * BLOCK_SIZE_BYTES) + (RGB_WIDTH_PIXELS - 1) + * * RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + * + * Loop over i decreasing (width) + * Each pix: + * in_block_ptr += RGB_WIDTH_BYTES + * out_block_ptr += 4 + * + * Each row of block: + * in_block_ptr -= RGB_WIDTH_BYTES * BLOCK_SIZE_PIXELS - 2 + * out_block_ptr -= RGB_HEIGHT_PIXELS * BYTES_PER_PIXEL + 2 * BLOCK_SIZE_PIXELS; + * + * It may perform vertical mirroring too depending on the vmirror flag. + */ +static int opl_rotate270_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + - BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS + : dst_line_stride + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS) * src_line_stride; + out_block_ptr = dst + (((height / BLOCK_SIZE_PIXELS) - k) + * BLOCK_SIZE_PIXELS * BYTES_PER_PIXEL) + + (width - 1) * dst_line_stride; + + /* + * For vertical mirroring the writing starts from the + * first line + */ + if (vmirror) + out_block_ptr -= dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], %4\n\t" + "ldrh r3, [%0], %4\n\t" + "ldrh r4, [%0], %4\n\t" + "ldrh r5, [%0], %4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + :"+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + :"0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + :"r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr -= IN_INDEX; + out_block_ptr -= OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_rotate270_u16); +EXPORT_SYMBOL(opl_rotate270_vmirror_u16); diff --git a/drivers/media/video/mxc/opl/rotate270_u16_qcif.S b/drivers/media/video/mxc/opl/rotate270_u16_qcif.S new file mode 100644 index 000000000000..4101eaac4554 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate270_u16_qcif.S @@ -0,0 +1,70 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/linkage.h> + + .text + .align 2 +ENTRY(opl_rotate270_u16_qcif) + STMFD sp!,{r4-r10} + MOV r12,#0x160 + MOV r10,#0x90 + MOV r3,r10,LSR #4 +.L1.16: + RSB r2,r3,r10,LSR #4 + MOV r5,r2,LSL #5 + MOV r4,r12,LSR #1 + SMULBB r4,r5,r4 + ADD r2,r1,r2,LSL #5 + ADD r5,r2,#0xc000 + ADD r5,r5,#0x4e0 + MOV r2,r12,LSR #1 + ADD r4,r0,r4 +.L1.52: + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + SUBS r2,r2,#1 + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],r12 + LDRH r7,[r4],r12 + LDRH r8,[r4],r12 + LDRH r9,[r4],r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + SUB r4,r4,#0x1500 + STMIA r5,{r6,r7} + SUB r5,r5,#0x138 + SUB r4,r4,#0xfe + BGT .L1.52 + SUBS r3,r3,#1 + BGT .L1.16 + LDMFD sp!,{r4-r10} + BX lr + .size opl_rotate270_u16_qcif, . - opl_rotate270_u16_qcif diff --git a/drivers/media/video/mxc/opl/rotate90_u16.c b/drivers/media/video/mxc/opl/rotate90_u16.c new file mode 100644 index 000000000000..dd7d445aa952 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate90_u16.c @@ -0,0 +1,220 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include "opl.h" + +static int opl_rotate90_u16_by16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_rotate90_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror); +static int opl_rotate90_vmirror_u16_both(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror); +int opl_rotate90_u16_qcif(const u8 * src, u8 * dst); + +int opl_rotate90_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride) +{ + return opl_rotate90_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 0); +} + +int opl_rotate90_vmirror_u16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride) +{ + return opl_rotate90_vmirror_u16_both(src, src_line_stride, width, + height, dst, dst_line_stride, 1); +} + +static int opl_rotate90_vmirror_u16_both(const u8 * src, int src_line_stride, + int width, int height, u8 * dst, + int dst_line_stride, int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int BLOCK_SIZE_PIXELS_BY4 = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + /* The QCIF algorithm doesn't support vertical mirroring */ + if (vmirror == 0 && width == QCIF_Y_WIDTH && height == QCIF_Y_HEIGHT + && src_line_stride == QCIF_Y_WIDTH * 2 + && src_line_stride == QCIF_Y_HEIGHT * 2) + return opl_rotate90_u16_qcif(src, dst); + else if (width % BLOCK_SIZE_PIXELS == 0 + && height % BLOCK_SIZE_PIXELS == 0) + return opl_rotate90_u16_by16(src, src_line_stride, width, + height, dst, dst_line_stride, + vmirror); + else if (width % BLOCK_SIZE_PIXELS_BY4 == 0 + && height % BLOCK_SIZE_PIXELS_BY4 == 0) + return opl_rotate90_u16_by4(src, src_line_stride, width, height, + dst, dst_line_stride, vmirror); + else + return OPLERR_BAD_ARG; +} + +/* + * Performs clockwise rotation (and possibly vertical mirroring depending + * on the vmirror flag) using block sizes of 16x16 + * The algorithm is similar to 270 degree clockwise rotation algorithm + */ +static int opl_rotate90_u16_by16(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL; + const int BLOCK_SIZE_BYTES = BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + + BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride - BLOCK_SIZE_BYTES + : dst_line_stride - BLOCK_SIZE_BYTES; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + src_line_stride * (height - 1) + - (src_line_stride * BLOCK_SIZE_PIXELS * + (height / BLOCK_SIZE_PIXELS - k)); + out_block_ptr = dst + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS * + ((height / BLOCK_SIZE_PIXELS) - k); + + /* + * For vertical mirroring the writing starts from the + * bottom line + */ + if (vmirror) + out_block_ptr += dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + :"+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + :"0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + :"r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr += IN_INDEX; + out_block_ptr += OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +/* + * Performs clockwise rotation (and possibly vertical mirroring depending + * on the vmirror flag) using block sizes of 4x4 + * The algorithm is similar to 270 degree clockwise rotation algorithm + */ +static int opl_rotate90_u16_by4(const u8 * src, int src_line_stride, int width, + int height, u8 * dst, int dst_line_stride, + int vmirror) +{ + const int BLOCK_SIZE_PIXELS = CACHE_LINE_WORDS * BYTES_PER_WORD + / BYTES_PER_PIXEL / 4; + const int BLOCK_SIZE_BYTES = BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS; + const int IN_INDEX = src_line_stride * BLOCK_SIZE_PIXELS + + BYTES_PER_PIXEL; + const int OUT_INDEX = vmirror ? + -dst_line_stride - BLOCK_SIZE_BYTES + : dst_line_stride - BLOCK_SIZE_BYTES; + const u8 *in_block_ptr; + u8 *out_block_ptr; + int i, k; + + for (k = height / BLOCK_SIZE_PIXELS; k > 0; k--) { + in_block_ptr = src + src_line_stride * (height - 1) + - (src_line_stride * BLOCK_SIZE_PIXELS * + (height / BLOCK_SIZE_PIXELS - k)); + out_block_ptr = dst + BYTES_PER_PIXEL * BLOCK_SIZE_PIXELS + * ((height / BLOCK_SIZE_PIXELS) - k); + + /* + * For horizontal mirroring the writing starts from the + * bottom line + */ + if (vmirror) + out_block_ptr += dst_line_stride * (width - 1); + + for (i = width; i > 0; i--) { + __asm__ volatile ( + "ldrh r2, [%0], -%4\n\t" + "ldrh r3, [%0], -%4\n\t" + "ldrh r4, [%0], -%4\n\t" + "ldrh r5, [%0], -%4\n\t" + "orr r2, r2, r3, lsl #16\n\t" + "orr r4, r4, r5, lsl #16\n\t" + "str r2, [%1], #4\n\t" + "str r4, [%1], #4\n\t" + + :"+r" (in_block_ptr), "+r"(out_block_ptr) /* output */ + :"0"(in_block_ptr), "1"(out_block_ptr), "r"(src_line_stride) /* input */ + :"r2", "r3", "r4", "r5", "memory" /* modify */ + ); + in_block_ptr += IN_INDEX; + out_block_ptr += OUT_INDEX; + } + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_rotate90_u16); +EXPORT_SYMBOL(opl_rotate90_vmirror_u16); diff --git a/drivers/media/video/mxc/opl/rotate90_u16_qcif.S b/drivers/media/video/mxc/opl/rotate90_u16_qcif.S new file mode 100644 index 000000000000..8568a9e629e5 --- /dev/null +++ b/drivers/media/video/mxc/opl/rotate90_u16_qcif.S @@ -0,0 +1,71 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/linkage.h> + + .text + .align 2 +ENTRY(opl_rotate90_u16_qcif) + STMFD sp!,{r4-r10} + MOV r12,#0x160 + MOV r10,#0x90 + MOV r3,r10,LSR #4 +.L1.216: + RSB r2,r3,r10,LSR #4 + MOV r4,#0x20 + SMULBB r5,r4,r2 + MOV r4,#0x1600 + SMULBB r2,r4,r2 + ADD r4,r0,#0xc000 + ADD r4,r4,#0x4a0 + SUB r4,r4,r2 + MOV r2,r12,LSR #1 + ADD r5,r1,r5 +.L1.256: + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + SUBS r2,r2,#1 + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + STMIA r5!,{r6,r7} + LDRH r6,[r4],-r12 + LDRH r7,[r4],-r12 + LDRH r8,[r4],-r12 + LDRH r9,[r4],-r12 + ORR r6,r6,r7,LSL #16 + ORR r7,r8,r9,LSL #16 + ADD r4,r4,#0x1600 + STMIA r5!,{r6,r7} + ADD r5,r5,#0x100 + ADD r4,r4,#2 + BGT .L1.256 + SUBS r3,r3,#1 + BGT .L1.216 + LDMFD sp!,{r4-r10} + BX lr + .size opl_rotate90_u16_qcif, . - opl_rotate90_u16_qcif diff --git a/drivers/media/video/mxc/opl/vmirror_u16.c b/drivers/media/video/mxc/opl/vmirror_u16.c new file mode 100644 index 000000000000..57f805c08a81 --- /dev/null +++ b/drivers/media/video/mxc/opl/vmirror_u16.c @@ -0,0 +1,46 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/string.h> +#include "opl.h" + +int opl_vmirror_u16(const u8 * src, int src_line_stride, int width, int height, + u8 * dst, int dst_line_stride) +{ + const u8 *src_row_addr; + u8 *dst_row_addr; + int i; + + if (!src || !dst) + return OPLERR_NULL_PTR; + + if (width == 0 || height == 0 || src_line_stride == 0 + || dst_line_stride == 0) + return OPLERR_BAD_ARG; + + src_row_addr = src; + dst_row_addr = dst + (height - 1) * dst_line_stride; + + /* Loop over all rows */ + for (i = 0; i < height; i++) { + /* memcpy each row */ + memcpy(dst_row_addr, src_row_addr, BYTES_PER_PIXEL * width); + src_row_addr += src_line_stride; + dst_row_addr -= dst_line_stride; + } + + return OPLERR_SUCCESS; +} + +EXPORT_SYMBOL(opl_vmirror_u16); diff --git a/drivers/media/video/mxc/output/Kconfig b/drivers/media/video/mxc/output/Kconfig new file mode 100644 index 000000000000..2153ad248907 --- /dev/null +++ b/drivers/media/video/mxc/output/Kconfig @@ -0,0 +1,28 @@ +config VIDEO_MXC_IPU_OUTPUT + bool "IPU v4l2 support" + depends on VIDEO_MXC_OUTPUT && MXC_IPU + default y + ---help--- + This is the video4linux2 driver for IPU post processing video output. + +config VIDEO_MXC_IPUV1_WVGA_OUTPUT + bool "IPUv1 WVGA v4l2 display support" + depends on VIDEO_MXC_OUTPUT && MXC_IPU + default n + ---help--- + This is the video4linux2 driver for IPUv1 WVGA post processing video output. + +config VIDEO_MXC_EMMA_OUTPUT + bool + depends on VIDEO_MXC_OUTPUT && MXC_EMMA && FB_MXC_SYNC_PANEL + default y + ---help--- + This is the video4linux2 driver for EMMA post processing video output. + +config VIDEO_MXC_OUTPUT_FBSYNC + bool "Synchronize the output with LCDC refresh" + depends on VIDEO_MXC_EMMA_OUTPUT + default y + ---help--- + Synchronize the post-processing with LCDC EOF (End of Frame) to + prevent tearing issue. If unsure, say Y. diff --git a/drivers/media/video/mxc/output/Makefile b/drivers/media/video/mxc/output/Makefile new file mode 100644 index 000000000000..1713fa3bf3ab --- /dev/null +++ b/drivers/media/video/mxc/output/Makefile @@ -0,0 +1,11 @@ +ifeq ($(CONFIG_VIDEO_MXC_EMMA_OUTPUT),y) + mx27_output-objs := mx27_v4l2_output.o mx27_pp.o + obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mx27_output.o +endif + +ifeq ($(CONFIG_VIDEO_MXC_IPU_OUTPUT),y) + obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mxc_v4l2_output.o +endif +ifeq ($(CONFIG_VIDEO_MXC_IPUV1_WVGA_OUTPUT),y) + obj-$(CONFIG_VIDEO_MXC_OUTPUT) += mx31_v4l2_wvga_output.o +endif diff --git a/drivers/media/video/mxc/output/mx27_pp.c b/drivers/media/video/mxc/output/mx27_pp.c new file mode 100644 index 000000000000..a82328015fe2 --- /dev/null +++ b/drivers/media/video/mxc/output/mx27_pp.c @@ -0,0 +1,904 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_pp.c + * + * @brief MX27 V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MX27 eMMA Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/fb.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <asm/io.h> + +#include "mx27_pp.h" +#include "mxc_v4l2_output.h" + +#define SCALE_RETRY 32 /* to be more relax, less precise */ +#define PP_SKIP 1 +#define PP_TBL_MAX 40 + +static unsigned short scale_tbl[PP_TBL_MAX]; +static int g_hlen, g_vlen; + +static emma_pp_cfg g_pp_cfg; +static int g_disp_num = 0; +static char pp_dev[] = "emma_pp"; + +/*! + * @brief PP resizing routines + */ +static int gcd(int x, int y); +static int ratio(int x, int y, int *den); +static int scale_0d(int k, int coeff, int base, int nxt); +static int scale_1d(int inv, int outv, int k); +static int scale_1d_smart(int *inv, int *outv, int index); +static int scale_2d(emma_pp_scale * sz); + +static irqreturn_t pp_isr(int irq, void *dev_id); +static int set_output_addr(emma_pp_cfg * cfg, vout_data * vout); +static int pphw_reset(void); +static int pphw_enable(int flag); +static int pphw_ptr(emma_pp_cfg * cfg); +static int pphw_outptr(emma_pp_cfg * cfg); +static int pphw_cfg(emma_pp_cfg * cfg); +static int pphw_isr(void); +static void pphw_init(void); +static void pphw_exit(void); + +#define PP_DUMP(reg) pr_debug("%s\t = 0x%08X\n", #reg, __raw_readl(reg)) +void pp_dump(void) +{ + PP_DUMP(PP_CNTL); + PP_DUMP(PP_INTRCNTL); + PP_DUMP(PP_INTRSTATUS); + PP_DUMP(PP_SOURCE_Y_PTR); + PP_DUMP(PP_SOURCE_CB_PTR); + PP_DUMP(PP_SOURCE_CR_PTR); + PP_DUMP(PP_DEST_RGB_PTR); + PP_DUMP(PP_QUANTIZER_PTR); + PP_DUMP(PP_PROCESS_FRAME_PARA); + PP_DUMP(PP_SOURCE_FRAME_WIDTH); + PP_DUMP(PP_DEST_DISPLAY_WIDTH); + PP_DUMP(PP_DEST_IMAGE_SIZE); + PP_DUMP(PP_DEST_FRAME_FMT_CNTL); + PP_DUMP(PP_RESIZE_INDEX); + PP_DUMP(PP_CSC_COEF_0123); + PP_DUMP(PP_CSC_COEF_4); +} + +/*! + * @brief Set PP input address. + * @param ptr The pointer to the Y value of input + * @return Zero on success, others on failure + */ +int pp_ptr(unsigned long ptr) +{ + g_pp_cfg.ptr.y = ptr; + g_pp_cfg.ptr.u = g_pp_cfg.ptr.v = g_pp_cfg.ptr.qp = 0; + + return pphw_ptr(&g_pp_cfg); +} + +/*! + * @brief Enable or disable PP. + * @param flag Zero to disable PP, others to enable PP + * @return Zero on success, others on failure + */ +int pp_enable(int flag) +{ + return pphw_enable(flag); +} + +/*! + * @brief Get the display No. of last completed PP frame. + * @return The display No. of last completed PP frame. + */ +int pp_num_last(void) +{ + return (g_disp_num ? 0 : 1); +} + +/*! + * @brief Initialize PP. + * @param vout Pointer to _vout_data structure + * @return Zero on success, others on failure + */ +int pp_init(vout_data * vout) +{ + pphw_init(); + pphw_enable(0); + enable_irq(MXC_INT_EMMAPP); + return request_irq(MXC_INT_EMMAPP, pp_isr, 0, pp_dev, vout); +} + +/*! + * @brief Deinitialize PP. + * @param vout Pointer to _vout_data structure + */ +void pp_exit(vout_data * vout) +{ + disable_irq(MXC_INT_EMMAPP); + free_irq(MXC_INT_EMMAPP, vout); + pphw_enable(0); + pphw_exit(); +} + +/*! + * @brief Configure PP. + * @param vout Pointer to _vout_data structure + * @return Zero on success, others on failure + */ +int pp_cfg(vout_data * vout) +{ + if (!vout) + return -1; + + /* PP accepts YUV420 input only */ + if (vout->v2f.fmt.pix.pixelformat != V4L2_PIX_FMT_YUV420) { + pr_debug("unsupported pixel format.\n"); + return -1; + } + + g_pp_cfg.operation = 0; + + memset(g_pp_cfg.csc_table, 0, sizeof(g_pp_cfg.csc_table)); + + /* Convert output pixel format to PP required format */ + switch (vout->v4l2_fb.fmt.pixelformat) { + case V4L2_PIX_FMT_BGR32: + g_pp_cfg.red_width = 8; + g_pp_cfg.green_width = 8; + g_pp_cfg.blue_width = 8; + g_pp_cfg.red_offset = 8; + g_pp_cfg.green_offset = 16; + g_pp_cfg.blue_offset = 24; + g_pp_cfg.rgb_resolution = 32; + break; + case V4L2_PIX_FMT_RGB32: + g_pp_cfg.red_width = 8; + g_pp_cfg.green_width = 8; + g_pp_cfg.blue_width = 8; + g_pp_cfg.red_offset = 24; + g_pp_cfg.green_offset = 16; + g_pp_cfg.blue_offset = 8; + g_pp_cfg.rgb_resolution = 32; + break; + case V4L2_PIX_FMT_YUYV: + g_pp_cfg.red_width = 0; + g_pp_cfg.green_width = 0; + g_pp_cfg.blue_width = 0; + g_pp_cfg.red_offset = 0; + g_pp_cfg.green_offset = 0; + g_pp_cfg.blue_offset = PP_PIX_YUYV; + g_pp_cfg.rgb_resolution = 16; + break; + case V4L2_PIX_FMT_UYVY: + g_pp_cfg.red_width = 0; + g_pp_cfg.green_width = 0; + g_pp_cfg.blue_width = 0; + g_pp_cfg.red_offset = 0; + g_pp_cfg.green_offset = 0; + g_pp_cfg.blue_offset = PP_PIX_UYVY; + g_pp_cfg.rgb_resolution = 16; + break; + case V4L2_PIX_FMT_RGB565: + default: + g_pp_cfg.red_width = 5; + g_pp_cfg.green_width = 6; + g_pp_cfg.blue_width = 5; + g_pp_cfg.red_offset = 11; + g_pp_cfg.green_offset = 5; + g_pp_cfg.blue_offset = 0; + g_pp_cfg.rgb_resolution = 16; + break; + } + + if (vout->ipu_buf[0] != -1) + g_pp_cfg.ptr.y = + (unsigned int)vout->queue_buf_paddr[vout->ipu_buf[0]]; + else + g_pp_cfg.ptr.y = 0; + + g_pp_cfg.ptr.u = g_pp_cfg.ptr.v = g_pp_cfg.ptr.qp = 0; + + g_pp_cfg.dim.in.width = vout->v2f.fmt.pix.width; + g_pp_cfg.dim.in.height = vout->v2f.fmt.pix.height; + g_pp_cfg.dim.out.width = vout->crop_current.width; + g_pp_cfg.dim.out.height = vout->crop_current.height; + g_pp_cfg.dim.num.width = 0; + g_pp_cfg.dim.num.height = 0; + g_pp_cfg.dim.den.width = 0; + g_pp_cfg.dim.den.height = 0; + + if (scale_2d(&g_pp_cfg.dim)) { + pr_debug("unsupported resize ratio.\n"); + return -1; + } + + g_pp_cfg.dim.out.width = vout->crop_current.width; + g_pp_cfg.dim.out.height = vout->crop_current.height; + + g_pp_cfg.in_y_stride = 0; + if (set_output_addr(&g_pp_cfg, vout)) { + pr_debug("failed to set pp output address.\n"); + return -1; + } + + return pphw_cfg(&g_pp_cfg); +} + +irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id); + +/*! + * @brief PP IRQ handler. + */ +static irqreturn_t pp_isr(int irq, void *dev_id) +{ + int status; + vout_data *vout = dev_id; + + status = pphw_isr(); + if ((status & 0x1) == 0) { /* Not frame complete interrupt */ + pr_debug("not pp frame complete interrupt\n"); + return IRQ_HANDLED; + } + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + g_disp_num = g_disp_num ? 0 : 1; + g_pp_cfg.outptr = (unsigned int)vout->display_bufs[g_disp_num]; + pphw_outptr(&g_pp_cfg); + } + + return mxc_v4l2out_pp_in_irq_handler(irq, dev_id); +} + +/*! + * @brief Set PP output address. + * @param cfg Pointer to emma_pp_cfg structure + * @param vout Pointer to _vout_data structure + * @return Zero on success, others on failure + */ +static int set_output_addr(emma_pp_cfg * cfg, vout_data * vout) +{ + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + g_disp_num = 0; + cfg->outptr = (unsigned int)vout->display_bufs[g_disp_num]; + cfg->out_stride = vout->crop_current.width; + return 0; + } else { + struct fb_info *fb; + + fb = registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + if (!fb) + return -1; + + cfg->outptr = fb->fix.smem_start; + cfg->outptr += vout->crop_current.top * fb->var.xres_virtual + * (fb->var.bits_per_pixel >> 3) + + vout->crop_current.left * (fb->var.bits_per_pixel >> 3); + cfg->out_stride = fb->var.xres_virtual; + + return 0; + } +} + +/*! + * @brief Get maximum common divisor. + * @param x First input value + * @param y Second input value + * @return Maximum common divisor of x and y + */ +static int gcd(int x, int y) +{ + int k; + + if (x < y) { + k = x; + x = y; + y = k; + } + + while ((k = x % y)) { + x = y; + y = k; + } + + return y; +} + +/*! + * @brief Get ratio. + * @param x First input value + * @param y Second input value + * @param den Denominator of the ratio (corresponding to y) + * @return Numerator of the ratio (corresponding to x) + */ +static int ratio(int x, int y, int *den) +{ + int g; + + if (!x || !y) + return 0; + + g = gcd(x, y); + *den = y / g; + + return x / g; +} + +/*! + * @brief Build PP coefficient entry + * Build one or more coefficient entries for PP coefficient table based + * on given coefficient. + * + * @param k The index of the coefficient in coefficient table + * @param coeff The weighting coefficient + * @param base The base of the coefficient + * @param nxt Number of pixels to be read + * + * @return The index of the next coefficient entry on success + * -1 on failure + */ +static int scale_0d(int k, int coeff, int base, int nxt) +{ + if (k >= PP_TBL_MAX) { + /* no more space in table */ + pr_debug("no space in scale table, k = %d\n", k); + return -1; + } + + coeff = ((coeff << BC_COEF) + (base >> 1)) / base; + + /* + * Valid values for weighting coefficient are 0, 2 to 30, and 31. + * A value of 31 is treated as 32 and therefore 31 is an + * invalid co-efficient. + */ + if (coeff >= SZ_COEF - 1) + coeff--; + else if (coeff == 1) + coeff++; + coeff = coeff << BC_NXT; + + if (nxt < SZ_NXT) { + coeff |= nxt; + coeff <<= 1; + coeff |= 1; + } else { + /* + * src inc field is 2 bit wide, for 4+, use special + * code 0:0:1 to prevent dest inc + */ + coeff |= PP_SKIP; + coeff <<= 1; + coeff |= 1; + nxt -= PP_SKIP; + do { + pr_debug("tbl = %03X\n", coeff); + scale_tbl[k++] = coeff; + coeff = (nxt > PP_SKIP) ? PP_SKIP : nxt; + coeff <<= 1; + } while ((nxt -= PP_SKIP) > 0); + } + pr_debug("tbl = %03X\n", coeff); + scale_tbl[k++] = coeff; + + return k; +} + +/* + * @brief Build PP coefficient table + * Build PP coefficient table for one dimension (width or height) + * based on given input and output resolution + * + * @param inv input resolution + * @param outv output resolution + * @param k index of free table entry + * + * @return The index of the next free coefficient entry on success + * -1 on failure + */ +static int scale_1d(int inv, int outv, int k) +{ + int v; /* overflow counter */ + int coeff, nxt; /* table output */ + + if (inv == outv) + return scale_0d(k, 1, 1, 1); /* force scaling */ + + if (inv * 4 < outv) { + pr_debug("upscale err: ratio should be in range 1:1 to 1:4\n"); + return -1; + } + + v = 0; + if (inv < outv) { + /* upscale: mix <= 2 input pixels per output pixel */ + do { + coeff = outv - v; + v += inv; + if (v >= outv) { + v -= outv; + nxt = 1; + } else + nxt = 0; + pr_debug("upscale: coeff = %d/%d nxt = %d\n", coeff, + outv, nxt); + k = scale_0d(k, coeff, outv, nxt); + if (k < 0) + return -1; + } while (v); + } else if (inv >= 2 * outv) { + /* PP doesn't support resize ratio > 2:1 except 4:1. */ + if ((inv != 2 * outv) && (inv != 4 * outv)) + return -1; + /* downscale: >=2:1 bilinear approximation */ + coeff = inv - 2 * outv; + v = 0; + nxt = 0; + do { + v += coeff; + nxt = 2; + while (v >= outv) { + v -= outv; + nxt++; + } + pr_debug("downscale: coeff = 1/2 nxt = %d\n", nxt); + k = scale_0d(k, 1, 2, nxt); + if (k < 0) + return -1; + } while (v); + } else { + /* downscale: bilinear */ + int in_pos_inc = 2 * outv; + int out_pos = inv; + int out_pos_inc = 2 * inv; + int init_carry = inv - outv; + int carry = init_carry; + + v = outv + in_pos_inc; + do { + coeff = v - out_pos; + out_pos += out_pos_inc; + carry += out_pos_inc; + for (nxt = 0; v < out_pos; nxt++) { + v += in_pos_inc; + carry -= in_pos_inc; + } + pr_debug("downscale: coeff = %d/%d nxt = %d\n", coeff, + in_pos_inc, nxt); + k = scale_0d(k, coeff, in_pos_inc, nxt); + if (k < 0) + return -1; + } while (carry != init_carry); + } + return k; +} + +/* + * @brief Build PP coefficient table + * Build PP coefficient table for one dimension (width or height) + * based on given input and output resolution. The given input + * and output resolution might be not supported due to hardware + * limits. In this case this functin rounds the input and output + * to closest possible values and return them to caller. + * + * @param inv input resolution, might be modified after the call + * @param outv output resolution, might be modified after the call + * @param k index of free table entry + * + * @return The index of the next free coefficient entry on success + * -1 on failure + */ +static int scale_1d_smart(int *inv, int *outv, int index) +{ + int len, num, den, retry; + static int num1, den1; + + if (!inv || !outv) + return -1; + + /* Both should be non-zero */ + if (!(*inv) || !(*outv)) + return -1; + + retry = SCALE_RETRY; + + do { + num = ratio(*inv, *outv, &den); + pr_debug("num = %d, den = %d\n", num, den); + if (!num) + continue; + + if (index != 0) { + /* + * We are now resizing height. Check to see if the + * resize ratio for width can be reused by height + */ + if ((num == num1) && (den == den1)) + return index; + } + + if ((len = scale_1d(num, den, index)) < 0) + /* increase output dimension to try another ratio */ + (*outv)++; + else { + if (index == 0) { + /* + * We are now resizing width. The same resize + * ratio may be reused by height, so save the + * ratio. + */ + num1 = num; + den1 = den; + } + return len; + } + } while (retry--); + + pr_debug("pp scale err\n"); + return -1; +} + +/* + * @brief Build PP coefficient table for both width and height + * Build PP coefficient table for both width and height based on + * given resizing ratios. + * + * @param sz Structure contains resizing ratio informations + * + * @return 0 on success, others on failure + */ +static int scale_2d(emma_pp_scale * sz) +{ + int inv, outv; + + /* horizontal resizing. parameter check - must provide in size */ + if (!sz->in.width) + return -1; + + /* Resizing based on num:den */ + inv = sz->num.width; + outv = sz->den.width; + + if ((g_hlen = scale_1d_smart(&inv, &outv, 0)) > 0) { + /* Resizing succeeded */ + sz->den.width = outv; + sz->out.width = (sz->in.width * outv) / inv; + } else { + /* Resizing based on in:out */ + inv = sz->in.width; + outv = sz->out.width; + + if ((g_hlen = scale_1d_smart(&inv, &outv, 0)) > 0) { + /* Resizing succeeded */ + sz->out.width = outv; + sz->num.width = ratio(sz->in.width, sz->out.width, + &sz->den.width); + } else + return -1; + } + + sz->out.width &= ~1; + + /* vertical resizing. parameter check - must provide in size */ + if (!sz->in.height) + return -1; + + /* Resizing based on num:den */ + inv = sz->num.height; + outv = sz->den.height; + + if ((g_vlen = scale_1d_smart(&inv, &outv, g_hlen)) > 0) { + /* Resizing succeeded */ + sz->den.height = outv; + sz->out.height = (sz->in.height * outv) / inv; + } else { + /* Resizing based on in:out */ + inv = sz->in.height; + outv = sz->out.height; + + if ((g_vlen = scale_1d_smart(&inv, &outv, g_hlen)) > 0) { + /* Resizing succeeded */ + sz->out.height = outv; + sz->num.height = ratio(sz->in.height, sz->out.height, + &sz->den.height); + } else + return -1; + } + + return 0; +} + +/*! + * @brief Set PP resizing registers. + * @param sz Pointer to pp scaling structure + * @return Zero on success, others on failure + */ +static int pphw_scale(emma_pp_scale * sz) +{ + __raw_writel((sz->out.width << 16) | sz->out.height, + PP_DEST_IMAGE_SIZE); + __raw_writel(((g_hlen - 1) << 16) | (g_vlen == + g_hlen ? 0 : (g_hlen << 8)) | + (g_vlen - 1), PP_RESIZE_INDEX); + for (g_hlen = 0; g_hlen < g_vlen; g_hlen++) + __raw_writel(scale_tbl[g_hlen], + PP_RESIZE_COEF_TBL + g_hlen * 4); + + return 0; +} + +/*! + * @brief Reset PP. + * @return Zero on success, others on failure + */ +static int pphw_reset(void) +{ + int i; + + __raw_writel(0x100, PP_CNTL); + + /* timeout */ + for (i = 0; i < 1000; i++) { + if (!(__raw_readl(PP_CNTL) & 0x100)) { + pr_debug("pp reset over\n"); + break; + } + } + + /* check reset value */ + if (__raw_readl(PP_CNTL) != 0x876) { + pr_debug("pp reset value err = 0x%08X\n", __raw_readl(PP_CNTL)); + return -1; + } + + return 0; +} + +/*! + * @brief Enable or disable PP. + * @param flag Zero to disable PP, others to enable PP + * @return Zero on success, others on failure + */ +static int pphw_enable(int flag) +{ + int ret = 0; + + if (flag) + __raw_writel(__raw_readl(PP_CNTL) | 1, PP_CNTL); + else + ret = pphw_reset(); + + return ret; +} + +/*! + * @brief Set PP input address. + * @param cfg The pointer to PP configuration parameter + * @return Zero on success, others on failure + */ +static int pphw_ptr(emma_pp_cfg * cfg) +{ + if (!cfg->ptr.u) { + int size; + + /* yuv - packed */ + size = PP_CALC_Y_SIZE(cfg); + cfg->ptr.u = cfg->ptr.y + size; + cfg->ptr.v = cfg->ptr.u + (size >> 2); + + /* yuv packed with qp appended */ + if (!cfg->ptr.qp) + cfg->ptr.qp = cfg->ptr.v + (size >> 2); + } + __raw_writel(cfg->ptr.y, PP_SOURCE_Y_PTR); + __raw_writel(cfg->ptr.u, PP_SOURCE_CB_PTR); + __raw_writel(cfg->ptr.v, PP_SOURCE_CR_PTR); + __raw_writel(cfg->ptr.qp, PP_QUANTIZER_PTR); + + return 0; +} + +/*! + * @brief Set PP output address. + * @param cfg The pointer to PP configuration parameter + * @return Zero on success, others on failure + */ +static int pphw_outptr(emma_pp_cfg * cfg) +{ + __raw_writel(cfg->outptr, PP_DEST_RGB_PTR); + return 0; +} + +/*! + * @brief Configuration PP. + * @param cfg The pointer to PP configuration parameter + * @return Zero on success, others on failure + */ +static int pphw_cfg(emma_pp_cfg * cfg) +{ + int rt; + register int r; + + pphw_scale(&cfg->dim); + + if (!cfg->in_y_stride) + cfg->in_y_stride = cfg->dim.in.width; + + if (!cfg->out_stride) + cfg->out_stride = cfg->dim.out.width; + + r = __raw_readl(PP_CNTL) & ~EN_MASK; + + /* config parms */ + r |= cfg->operation & EN_MASK; + if (cfg->operation & EN_MACROBLOCK) { + /* Macroblock Mode */ + r |= 0x0200; + __raw_writel(0x06, PP_INTRCNTL); + } else { + /* Frame mode */ + __raw_writel(0x05, PP_INTRCNTL); + } + + if (cfg->red_width | cfg->green_width | cfg->blue_width) { + /* color conversion to be performed */ + r |= EN_CSC; + if (!(cfg->red_offset | cfg->green_offset)) { + /* auto offset B:G:R LSb to Msb */ + cfg->green_offset = cfg->blue_offset + cfg->blue_width; + cfg->red_offset = cfg->green_offset + cfg->green_width; + } + if (!cfg->rgb_resolution) { + /* derive minimum resolution required */ + int w, w2; + + w = cfg->red_offset + cfg->red_width; + w2 = cfg->blue_offset + cfg->blue_width; + if (w < w2) + w = w2; + w2 = cfg->green_offset + cfg->green_width; + if (w < w2) + w = w2; + if (w > 16) + w = 24; + else if (w > 8) + w = 16; + else + w = 8; + cfg->rgb_resolution = w; + } + /* 00,11 - 32 bpp, 10 - 16 bpp, 01 - 8 bpp */ + r &= ~0xC00; + if (cfg->rgb_resolution < 32) + r |= (cfg->rgb_resolution << 7); + __raw_writel((cfg->red_offset << 26) | + (cfg->green_offset << 21) | + (cfg->blue_offset << 16) | + (cfg->red_width << 8) | + (cfg->green_width << 4) | + cfg->blue_width, PP_DEST_FRAME_FMT_CNTL); + } else { + /* add YUV422 formatting */ + static const unsigned int _422[] = { + 0x62000888, + 0x60100888, + 0x43080888, + 0x41180888 + }; + + __raw_writel(_422[(cfg->blue_offset >> 3) & 3], + PP_DEST_FRAME_FMT_CNTL); + cfg->rgb_resolution = 16; + r &= ~0xC00; + r |= (cfg->rgb_resolution << 7); + } + + /* add csc formatting */ + if (!cfg->csc_table[1]) { + static const unsigned short _csc[][6] = { + {0x80, 0xb4, 0x2c, 0x5b, 0x0e4, 0}, + {0x95, 0xcc, 0x32, 0x68, 0x104, 1}, + {0x80, 0xca, 0x18, 0x3c, 0x0ec, 0}, + {0x95, 0xe5, 0x1b, 0x44, 0x10e, 1}, + }; + memcpy(cfg->csc_table, _csc[cfg->csc_table[0]], + sizeof(_csc[0])); + } + __raw_writel((cfg->csc_table[0] << 24) | + (cfg->csc_table[1] << 16) | + (cfg->csc_table[2] << 8) | + cfg->csc_table[3], PP_CSC_COEF_0123); + __raw_writel((cfg->csc_table[5] ? (1 << 9) : 0) | cfg->csc_table[4], + PP_CSC_COEF_4); + + __raw_writel(r, PP_CNTL); + + pphw_ptr(cfg); + pphw_outptr(cfg); + + /* + * #MB in a row = input_width / 16pix + * 1 byte per QP per MB + * QP must be formatted to be 4-byte aligned + * YUV lines are to be 4-byte aligned as well + * So Y is 8 byte aligned, as U = V = Y/2 for 420 + * MPEG MBs are 16x16 anyway + */ + __raw_writel((cfg->dim.in.width << 16) | cfg->dim.in.height, + PP_PROCESS_FRAME_PARA); + __raw_writel(cfg->in_y_stride | (PP_CALC_QP_WIDTH(cfg) << 16), + PP_SOURCE_FRAME_WIDTH); + + /* in bytes */ + rt = cfg->rgb_resolution >> 3; + if (rt == 3) + rt = 4; + __raw_writel(cfg->out_stride * rt, PP_DEST_DISPLAY_WIDTH); + + pp_dump(); + return 0; +} + +/*! + * @brief Check PP interrupt status. + * @return PP interrupt status + */ +static int pphw_isr(void) +{ + unsigned long status; + + pr_debug("pp: in isr.\n"); + status = __raw_readl(PP_INTRSTATUS) & 7; + if (!status) { + pr_debug("pp: not my isr err.\n"); + return status; + } + + if (status & 4) + pr_debug("pp: isr state error.\n"); + + /* clear interrupt status */ + __raw_writel(status, PP_INTRSTATUS); + + return status; +} + +static struct clk *emma_clk; + +/*! + * @brief PP module clock enable + */ +static void pphw_init(void) +{ + emma_clk = clk_get(NULL, "emma_clk"); + clk_enable(emma_clk); +} + +/*! + * @brief PP module clock disable + */ +static void pphw_exit(void) +{ + clk_disable(emma_clk); + clk_put(emma_clk); +} diff --git a/drivers/media/video/mxc/output/mx27_pp.h b/drivers/media/video/mxc/output/mx27_pp.h new file mode 100644 index 000000000000..7bc65ddda3a1 --- /dev/null +++ b/drivers/media/video/mxc/output/mx27_pp.h @@ -0,0 +1,180 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_pp.h + * + * @brief Header file for MX27 V4L2 Video Output Driver + * + * @ingroup MXC_V4L2_OUTPUT + */ +#ifndef __MX27_PP_H__ +#define __MX27_PP_H__ + +#include "mxc_v4l2_output.h" + +/* PP register definitions */ +#define PP_REG(ofs) (IO_ADDRESS(EMMA_BASE_ADDR) - 0x400 + ofs) + +/* Register offsets */ +#define PP_CNTL PP_REG(0x00) +#define PP_INTRCNTL PP_REG(0x04) +#define PP_INTRSTATUS PP_REG(0x08) +#define PP_SOURCE_Y_PTR PP_REG(0x0C) +#define PP_SOURCE_CB_PTR PP_REG(0x10) +#define PP_SOURCE_CR_PTR PP_REG(0x14) +#define PP_DEST_RGB_PTR PP_REG(0x18) +#define PP_QUANTIZER_PTR PP_REG(0x1C) +#define PP_PROCESS_FRAME_PARA PP_REG(0x20) +#define PP_SOURCE_FRAME_WIDTH PP_REG(0x24) +#define PP_DEST_DISPLAY_WIDTH PP_REG(0x28) +#define PP_DEST_IMAGE_SIZE PP_REG(0x2C) +#define PP_DEST_FRAME_FMT_CNTL PP_REG(0x30) +#define PP_RESIZE_INDEX PP_REG(0x34) +#define PP_CSC_COEF_0123 PP_REG(0x38) +#define PP_CSC_COEF_4 PP_REG(0x3C) +#define PP_RESIZE_COEF_TBL PP_REG(0x100) + +/* resize table dimensions + dest pixel index left/32 right/32 #src pixels to read + 0 [BC_COEF] [BC_COEF] [BC_NXT] + : + pp_tbl_max-1 +*/ +#define BC_NXT 2 +#define BC_COEF 5 +#define SZ_COEF (1 << BC_COEF) +#define SZ_NXT (1 << BC_NXT) + +/* PP operations */ +#define EN_DEBLOCK 0x02 +#define EN_DERING 0x04 +#define EN_CSC 0x10 +#define EN_MACROBLOCK 0x20 +#define EN_DEF 0x16 +#define EN_MASK 0x36 +#define EN_BIGDATA 0x1000 +#define EN_BIGQP 0x2000 + +/* PP CSC tables */ +#define CSC_TBL_NONE 0x80 +#define CSC_TBL_REUSE 0x81 +#define CSC_TBL_A1 0x00 +#define CSC_TBL_A0 0x20 +#define CSC_TBL_B1 0x40 +#define CSC_TBL_B0 0x60 +/* converts from 4 decimal fixed point to hw setting & vice versa */ +#define PP_CSC_FP4_2_HW(coeff) ((((coeff) << 7) + 5000) / 10000) +#define PP_CSC_HW_2_FP4(coeff) ((((coeff) * 10000) + 64) >> 7) + +#define PP_PIX_YUYV 0 +#define PP_PIX_YVYU 8 +#define PP_PIX_UYVY 16 +#define PP_PIX_VYUY 24 + +/* PP size & width calculation macros */ +#define PP_CALC_QP_WIDTH(cfg) \ + (!((cfg)->operation & (EN_DEBLOCK | EN_DERING)) ? 0 : \ + (((((cfg)->dim.in.width + 15) >> 4) + 3) & ~3)) +#define PP_CALC_Y_SIZE(cfg) \ + ((cfg)->in_y_stride * (cfg)->dim.in.height) +#define PP_CALC_CH_SIZE(cfg) (PP_CALC_Y_SIZE(cfg) >> 2) +#define PP_CALC_BPP(cfg) \ + ((cfg)->rgb_resolution > 16 ? 4 : ((cfg)->rgb_resolution >> 3)) +#define PP_CALC_YUV_SIZE(cfg) \ + ((PP_CALC_Y_SIZE(cfg) * 3) >> 1) +#define PP_CALC_QP_SIZE(cfg) \ + (PP_CALC_QP_WIDTH(cfg) * (((cfg)->dim.in.height + 15) >> 4)) +#define PP_CALC_DEST_WIDTH(cfg) \ + (((cfg)->out_stride & ~1) * PP_CALC_BPP(cfg)) +#define PP_CALC_DEST_SIZE(cfg) \ + ((cfg)->dim.out.height * PP_CALC_DEST_WIDTH(cfg)) + +/* + * physical addresses for bus mastering + * v=0 -> yuv packed + * v=0 & qp=0 -> yuv packed with qp appended + */ +typedef struct _emma_pp_ptr { + unsigned int y; /* Y data (line align8) */ + unsigned int u; /* U data (line align4) */ + unsigned int v; /* V data (line align4) */ + unsigned int qp; /* Quantization (line align4) */ +} emma_pp_ptr; + +typedef struct _emma_pp_size { + int width; + int height; +} emma_pp_size; + +/* + * if num.width != 0 + * resize ratio = num.width : den.width + * else + * resize ratio = in.width : out.width + * same for height + */ +typedef struct _emma_pp_scale { + emma_pp_size num; + emma_pp_size den; + emma_pp_size in; /* clip */ + emma_pp_size out; /* 0 -> same as in */ +} emma_pp_scale; + +typedef struct _emma_pp_cfg { + unsigned char operation; /* OR of EN_xx defines */ + + /* + * input color coeff + * fixed pt 8 bits, steps of 1/128 + * csc[5] is 1 or 0 to indicate Y + 16 + * csc[0] is matrix id 0-3 while csc[1-5]=0 + */ + unsigned short csc_table[6]; + + /* + * Output color (shade width, shade offset, pixel resolution) + * Eg. 16bpp RGB565 resolution, the values could be: + * red_width = 5, green_width = 6, blue_width = 6 + * red_offset = 11, green_offset = 5, blue_offset = 0 (defaults) + * rgb_resolution = 16 (default) + * For YUV422: xxx_width=0, blue_offset=PP_PIX_xxx + */ + unsigned short red_width; + unsigned short green_width; + unsigned short blue_width; + /* if offsets are 0, the offsets are by width LSb to MSb B:G:R */ + unsigned short red_offset; + unsigned short blue_offset; + unsigned short green_offset; + /* if resolution is 0, the minimum for the sum of widths is chosen */ + short rgb_resolution; /* 8,16,24 bpp only */ + + emma_pp_ptr ptr; /* dma buffer pointers */ + unsigned int outptr; /* RGB/YUV output */ + emma_pp_scale dim; /* in/out dimensions */ + + /* pixels between two adjacent input Y rows */ + unsigned short in_y_stride; /* 0 = in_width */ + /* PIXELS between two adjacent output rows */ + unsigned short out_stride; /* 0 = out_width */ +} emma_pp_cfg; + +int pp_ptr(unsigned long ptr); +int pp_enable(int flag); +int pp_cfg(vout_data * vout); +int pp_init(vout_data * vout); +int pp_num_last(void); +void pp_exit(vout_data * vout); + +#endif /* __MX27_PP_H__ */ diff --git a/drivers/media/video/mxc/output/mx27_v4l2_output.c b/drivers/media/video/mxc/output/mx27_v4l2_output.c new file mode 100644 index 000000000000..a00f92d8e979 --- /dev/null +++ b/drivers/media/video/mxc/output/mx27_v4l2_output.c @@ -0,0 +1,1442 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_v4l2_output.c + * + * @brief MX27 V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MX27 eMMA Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/semaphore.h> +#include <linux/poll.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> + +#include "mxc_v4l2_output.h" +#include "mx27_pp.h" +#include "../drivers/video/mxc/mx2fb.h" + +#define SDC_FG_FB_FORMAT V4L2_PIX_FMT_RGB565 + +struct v4l2_output mxc_outputs[1] = { + { + .index = 0, + .name = "DISP0 Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN}, +}; + +static int video_nr = 16; +static spinlock_t g_lock = SPIN_LOCK_UNLOCKED; +vout_data *g_vout; + +/* debug counters */ +uint32_t g_irq_cnt; +uint32_t g_buf_output_cnt; +uint32_t g_buf_q_cnt; +uint32_t g_buf_dq_cnt; + +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC +static uint32_t g_output_fb = -1; +static uint32_t g_fb_enabled = 0; +static uint32_t g_pp_ready = 0; + +static int fb_event_notify(struct notifier_block *self, + unsigned long action, void *data) +{ + struct fb_event *event = data; + struct fb_info *info = event->info; + unsigned long lock_flags; + int blank, i; + + for (i = 0; i < num_registered_fb; i++) + if (registered_fb[i] == info) + break; + + /* + * Check if the event is sent by the framebuffer in which + * the video is displayed. + */ + if (i != g_output_fb) + return 0; + + switch (action) { + case FB_EVENT_BLANK: + blank = *(int *)event->data; + spin_lock_irqsave(&g_lock, lock_flags); + g_fb_enabled = !blank; + if (blank && g_pp_ready) { + if (pp_enable(1)) + pr_debug("unable to enable PP\n"); + g_pp_ready = 0; + } + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + case FB_EVENT_MXC_EOF: + spin_lock_irqsave(&g_lock, lock_flags); + g_fb_enabled = 1; + if (g_pp_ready) { + if (pp_enable(1)) + pr_debug("unable to enable PP\n"); + g_pp_ready = 0; + } + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + + return 0; +} + +static struct notifier_block fb_event_notifier = { + .notifier_call = fb_event_notify, +}; + +static struct notifier_block mx2fb_event_notifier = { + .notifier_call = fb_event_notify, +}; +#endif + +#define QUEUE_SIZE (MAX_FRAME_NUM + 1) +static __inline int queue_size(v4l_queue * q) +{ + if (q->tail >= q->head) + return (q->tail - q->head); + else + return ((q->tail + QUEUE_SIZE) - q->head); +} + +static __inline int queue_buf(v4l_queue * q, int idx) +{ + if (((q->tail + 1) % QUEUE_SIZE) == q->head) + return -1; /* queue full */ + q->list[q->tail] = idx; + q->tail = (q->tail + 1) % QUEUE_SIZE; + return 0; +} + +static __inline int dequeue_buf(v4l_queue * q) +{ + int ret; + if (q->tail == q->head) + return -1; /* queue empty */ + ret = q->list[q->head]; + q->head = (q->head + 1) % QUEUE_SIZE; + return ret; +} + +static __inline int peek_next_buf(v4l_queue * q) +{ + if (q->tail == q->head) + return -1; /* queue empty */ + return q->list[q->head]; +} + +static __inline unsigned long get_jiffies(struct timeval *t) +{ + struct timeval cur; + + if (t->tv_usec >= 1000000) { + t->tv_sec += t->tv_usec / 1000000; + t->tv_usec = t->tv_usec % 1000000; + } + + do_gettimeofday(&cur); + if ((t->tv_sec < cur.tv_sec) + || ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec))) + return jiffies; + + if (t->tv_usec < cur.tv_usec) { + cur.tv_sec = t->tv_sec - cur.tv_sec - 1; + cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec; + } else { + cur.tv_sec = t->tv_sec - cur.tv_sec; + cur.tv_usec = t->tv_usec - cur.tv_usec; + } + + return jiffies + timeval_to_jiffies(&cur); +} + +/*! + * Private function to free buffers + * + * @param bufs_paddr Array of physical address of buffers to be freed + * + * @param bufs_vaddr Array of virtual address of buffers to be freed + * + * @param num_buf Number of buffers to be freed + * + * @param size Size for each buffer to be free + * + * @return status 0 success. + */ +static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + if (bufs_vaddr[i] != 0) { + dma_free_coherent(0, size, bufs_vaddr[i], + bufs_paddr[i]); + pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]); + bufs_paddr[i] = 0; + bufs_vaddr[i] = NULL; + } + } + return 0; +} + +/*! + * Private function to allocate buffers + * + * @param bufs_paddr Output array of physical address of buffers allocated + * + * @param bufs_vaddr Output array of virtual address of buffers allocated + * + * @param num_buf Input number of buffers to allocate + * + * @param size Input size for each buffer to allocate + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + bufs_vaddr[i] = dma_alloc_coherent(0, size, + &bufs_paddr[i], + GFP_DMA | GFP_KERNEL); + + if (bufs_vaddr[i] == 0) { + mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size); + pr_debug("dma_alloc_coherent failed.\n"); + return -ENOBUFS; + } + pr_debug("allocated @ paddr=0x%08X, size=%d.\n", + (u32) bufs_paddr[i], size); + } + + return 0; +} + +static void mxc_v4l2out_timer_handler(unsigned long arg) +{ + int index; + unsigned long timeout; + unsigned long lock_flags; + vout_data *vout = (vout_data *) arg; + + pr_debug("timer handler: %lu\n", jiffies); + + spin_lock_irqsave(&g_lock, lock_flags); + + if ((vout->state == STATE_STREAM_OFF) + || (vout->state == STATE_STREAM_STOPPING)) { + pr_debug("stream has stopped\n"); + goto exit0; + } + + /* + * If timer occurs before PP h/w is ready, then set the state to + * paused and the timer will be set again when next buffer is queued + * or PP completes. + */ + if (vout->ipu_buf[0] != -1) { + pr_debug("buffer is busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* Dequeue buffer and pass to PP */ + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + pr_debug("mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit0; + } + + g_buf_dq_cnt++; + vout->frame_count++; + vout->ipu_buf[0] = index; + + if (pp_ptr((unsigned int)vout->queue_buf_paddr[index])) { + pr_debug("unable to update buffer\n"); + goto exit0; + } +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC + if (g_fb_enabled && (vout->v4l2_fb.flags != V4L2_FBUF_FLAG_OVERLAY)) + g_pp_ready = 1; + else if (pp_enable(1)) { + pr_debug("unable to enable PP\n"); + goto exit0; + } +#else + if (pp_enable(1)) { + pr_debug("unable to enable PP\n"); + goto exit0; + } +#endif + pr_debug("enabled index %d\n", index); + + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + pr_debug("next index %d\n", index); + if (index != -1) { + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } else { + vout->state = STATE_STREAM_PAUSED; + pr_debug("timer handler paused\n"); + } + + exit0: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id) +{ + int last_buf; + int index; + unsigned long timeout; + unsigned long lock_flags; + vout_data *vout = dev_id; + + spin_lock_irqsave(&g_lock, lock_flags); + + g_irq_cnt++; + + if ((vout->state == STATE_STREAM_OFF) + || (vout->state == STATE_STREAM_STOPPING)) { + spin_unlock_irqrestore(&g_lock, lock_flags); + return IRQ_HANDLED; + } + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + gwinfo.enabled = 1; + gwinfo.alpha_value = 255; + gwinfo.ck_enabled = 0; + gwinfo.xpos = vout->crop_current.left; + gwinfo.ypos = vout->crop_current.top; + gwinfo.base = (unsigned long)vout->display_bufs[pp_num_last()]; + gwinfo.xres = vout->crop_current.width; + gwinfo.yres = vout->crop_current.height; + gwinfo.xres_virtual = vout->crop_current.width; + gwinfo.vs_reversed = 0; + + mx2_gw_set(&gwinfo); + } + + /* Process previous buffer */ + last_buf = vout->ipu_buf[0]; + pr_debug("last_buf %d g_irq_cnt %d\n", last_buf, g_irq_cnt); + if (last_buf != -1) { + g_buf_output_cnt++; + vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, last_buf); + vout->ipu_buf[0] = -1; + wake_up_interruptible(&vout->v4l_bufq); + } + + /* Setup timer for next buffer, when stream has been paused */ + if ((vout->state == STATE_STREAM_PAUSED) + && ((index = peek_next_buf(&vout->ready_q)) != -1)) { + pr_debug("next index %d\n", index); + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + vout->state = STATE_STREAM_ON; + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return IRQ_HANDLED; +} + +/*! + * Start the output stream + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamon(vout_data * vout) +{ + unsigned long timeout; + int index; + + if (!vout) + return -EINVAL; + + if (vout->state != STATE_STREAM_OFF) + return -EBUSY; + + if (queue_size(&vout->ready_q) < 1) { + pr_debug("no buffers queued yet!\n"); + return -EINVAL; + } + + vout->ipu_buf[0] = -1; + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + /* Free previously allocated buffer */ + mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr, + 2, vout->display_buf_size); + /* Allocate buffers for foreground */ + if (mxc_allocate_buffers(vout->display_bufs, + vout->display_bufs_vaddr, 2, + vout->display_buf_size) < 0) { + pr_debug("unable to allocate SDC FG buffers\n"); + return -ENOMEM; + } + } + + /* Configure PP */ + if (pp_cfg(vout)) { + /* Free previously allocated buffer */ + mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr, + 2, vout->display_buf_size); + pr_debug("failed to config PP.\n"); + return -EINVAL; + } +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC + g_output_fb = vout->output_fb_num[vout->cur_disp_output]; + g_fb_enabled = 0; + g_pp_ready = 0; + fb_register_client(&fb_event_notifier); + mx2fb_register_client(&mx2fb_event_notifier); +#endif + vout->frame_count = 0; + vout->state = STATE_STREAM_ON; + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = jiffies; + else + timeout = get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + vout->start_jiffies = vout->output_timer.expires = timeout; + pr_debug("STREAMON:Add timer %d timeout @ %lu jiffies, current = %lu\n", + index, timeout, jiffies); + add_timer(&vout->output_timer); + + return 0; +} + +/*! + * Shut down the voutera + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamoff(vout_data * vout) +{ + int i, retval = 0; + unsigned long lock_flag = 0; + + if (!vout) + return -EINVAL; + + if (vout->state == STATE_STREAM_OFF) { + return 0; + } + + spin_lock_irqsave(&g_lock, lock_flag); + + del_timer(&vout->output_timer); + pp_enable(0); /* Disable PP */ + + if (vout->state == STATE_STREAM_ON) { + vout->state = STATE_STREAM_STOPPING; + } + + spin_unlock_irqrestore(&g_lock, lock_flag); + + vout->ready_q.head = vout->ready_q.tail = 0; + vout->done_q.head = vout->done_q.tail = 0; + for (i = 0; i < vout->buffer_cnt; i++) { + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + + vout->state = STATE_STREAM_OFF; + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + /* Disable graphic window */ + gwinfo.enabled = 0; + mx2_gw_set(&gwinfo); + } +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC + g_output_fb = -1; + g_fb_enabled = 0; + g_pp_ready = 0; + fb_unregister_client(&fb_event_notifier); + mx2fb_unregister_client(&mx2fb_event_notifier); +#endif + + mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + return retval; +} + +/* + * Valid whether the palette is supported + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 1 if supported, 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return (palette == V4L2_PIX_FMT_YUV420); +} + +/* + * Returns bits per pixel for given pixel format + * + * @param pixelformat V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return bits per pixel of pixelformat + */ +static u32 fmt_to_bpp(u32 pixelformat) +{ + u32 bpp; + + switch (pixelformat) { + case V4L2_PIX_FMT_RGB565: + bpp = 16; + break; + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_RGB24: + bpp = 24; + break; + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_RGB32: + bpp = 32; + break; + default: + bpp = 8; + break; + } + return bpp; +} + +/* + * V4L2 - Handles VIDIOC_G_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_g_fmt(vout_data * vout, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + return -EINVAL; + } + *f = vout->v2f; + return 0; +} + +/* + * V4L2 - Handles VIDIOC_S_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_s_fmt(vout_data * vout, struct v4l2_format *f) +{ + int retval = 0; + u32 size = 0; + u32 bytesperline; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + goto err0; + } + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_debug("pixel format not supported\n"); + retval = -EINVAL; + goto err0; + } + + bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) / + 8; + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV422P: + /* byteperline for YUV planar formats is for + Y plane only */ + size = bytesperline * f->fmt.pix.height * 2; + break; + case V4L2_PIX_FMT_YUV420: + size = (bytesperline * f->fmt.pix.height * 3) / 2; + break; + default: + size = bytesperline * f->fmt.pix.height; + break; + } + + /* Return the actual size of the image to the app */ + f->fmt.pix.sizeimage = size; + + vout->v2f.fmt.pix.sizeimage = size; + vout->v2f.fmt.pix.width = f->fmt.pix.width; + vout->v2f.fmt.pix.height = f->fmt.pix.height; + vout->v2f.fmt.pix.pixelformat = f->fmt.pix.pixelformat; + vout->v2f.fmt.pix.bytesperline = f->fmt.pix.bytesperline; + + retval = 0; + err0: + return retval; +} + +/* + * V4L2 - Handles VIDIOC_G_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0; + case V4L2_CID_VFLIP: + return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0; + case (V4L2_CID_PRIVATE_BASE + 1): + return vout->rotate; + default: + return -EINVAL; + } +} + +/* + * V4L2 - Handles VIDIOC_S_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + case V4L2_CID_MXC_ROT: + return 0; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 interface - open function + * + * @param inode structure inode * + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l2out_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + int err; + + if (!vout) { + pr_info("Internal error, vout_data not found!\n"); + return -ENODEV; + } + + down(&vout->busy_lock); + + err = -EINTR; + if (signal_pending(current)) + goto oops; + + if (vout->open_count++ == 0) { + pp_init(vout); + + init_waitqueue_head(&vout->v4l_bufq); + + init_timer(&vout->output_timer); + vout->output_timer.function = mxc_v4l2out_timer_handler; + vout->output_timer.data = (unsigned long)vout; + + vout->state = STATE_STREAM_OFF; + g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0; + + } + + file->private_data = dev; + up(&vout->busy_lock); + return 0; + + oops: + up(&vout->busy_lock); + return err; +} + +/*! + * V4L2 interface - close function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l2out_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = file->private_data; + vout_data *vout = video_get_drvdata(dev); + + if (--vout->open_count == 0) { + pr_debug("release resource\n"); + + pp_exit(vout); + if (vout->state != STATE_STREAM_OFF) + mxc_v4l2out_streamoff(vout); + + file->private_data = NULL; + + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, vout->queue_buf_size); + vout->buffer_cnt = 0; + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + /* capture off */ + wake_up_interruptible(&vout->v4l_bufq); + } + + return 0; +} + +/*! + * V4L2 interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l2out_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = file->private_data; + vout_data *vout = video_get_drvdata(dev); + int retval = 0; + int i = 0; + + if (!vout) + return -EBADF; + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2_output"); + cap->version = 0; + cap->capabilities = + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *gf = arg; + retval = mxc_v4l2out_g_fmt(vout, gf); + break; + } + case VIDIOC_S_FMT: + { + struct v4l2_format *sf = arg; + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + retval = mxc_v4l2out_s_fmt(vout, sf); + break; + } + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (req->memory != V4L2_MEMORY_MMAP)) { + pr_debug + ("VIDIOC_REQBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + + if (req->count == 0) + mxc_v4l2out_streamoff(vout); + + if (vout->state == STATE_STREAM_OFF) { + if (vout->queue_buf_paddr[0] != 0) { + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + pr_debug + ("VIDIOC_REQBUFS: freed buffers\n"); + } + vout->buffer_cnt = 0; + } else { + pr_debug("VIDIOC_REQBUFS: Buffer is in use\n"); + retval = -EBUSY; + break; + } + + if (req->count == 0) + break; + + if (req->count < MIN_FRAME_NUM) { + req->count = MIN_FRAME_NUM; + } else if (req->count > MAX_FRAME_NUM) { + req->count = MAX_FRAME_NUM; + } + vout->buffer_cnt = req->count; + vout->queue_buf_size = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + + retval = mxc_allocate_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + if (retval < 0) + break; + + /* Init buffer queues */ + vout->done_q.head = 0; + vout->done_q.tail = 0; + vout->ready_q.head = 0; + vout->ready_q.tail = 0; + + for (i = 0; i < vout->buffer_cnt; i++) { + memset(&(vout->v4l2_bufs[i]), 0, + sizeof(vout->v4l2_bufs[i])); + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP; + vout->v4l2_bufs[i].index = i; + vout->v4l2_bufs[i].type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + vout->v4l2_bufs[i].length = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + vout->v4l2_bufs[i].m.offset = + (unsigned long)vout->queue_buf_paddr[i]; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + break; + } + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + u32 type = buf->type; + int index = buf->index; + + if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + pr_debug + ("VIDIOC_QUERYBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + down(&vout->param_lock); + memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf)); + up(&vout->param_lock); + break; + } + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int index = buf->index; + unsigned long lock_flags; + unsigned long timeout; + + if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt) || (buf->flags != 0)) { + retval = -EINVAL; + break; + } + + pr_debug("VIDIOC_QBUF: %d\n", buf->index); + + spin_lock_irqsave(&g_lock, lock_flags); + + memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf)); + vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED; + + g_buf_q_cnt++; + queue_buf(&vout->ready_q, index); + + if (vout->state == STATE_STREAM_PAUSED) { + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == + 0) + && (vout->v4l2_bufs[index].timestamp. + tv_usec == 0)) + timeout = + vout->start_jiffies + + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index]. + timestamp); + + if (jiffies >= timeout) { + pr_debug + ("warning: timer timeout already expired.\n"); + } + + vout->output_timer.expires = timeout; + pr_debug + ("QBUF:Add timer %d timeout @ %lu jiffies, " + "current = %lu\n", index, timeout, + jiffies); + add_timer(&vout->output_timer); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int idx; + + pr_debug("VIDIOC_DQBUF: q size = %d\n", + queue_size(&vout->done_q)); + + if ((queue_size(&vout->done_q) == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + if (!wait_event_interruptible_timeout(vout->v4l_bufq, + queue_size(&vout-> + done_q) + != 0, 10 * HZ)) { + pr_debug("VIDIOC_DQBUF: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + pr_debug("VIDIOC_DQBUF: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + idx = dequeue_buf(&vout->done_q); + if (idx == -1) { /* No frame free */ + pr_debug + ("VIDIOC_DQBUF: no free buffers, returning\n"); + retval = -EAGAIN; + break; + } + if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) == + 0) + pr_debug + ("VIDIOC_DQBUF: buffer in done q, but not " + "flagged as done\n"); + + vout->v4l2_bufs[idx].flags = 0; + memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf)); + pr_debug("VIDIOC_DQBUF: %d\n", buf->index); + break; + } + case VIDIOC_STREAMON: + { + retval = mxc_v4l2out_streamon(vout); + break; + } + case VIDIOC_STREAMOFF: + { + retval = mxc_v4l2out_streamoff(vout); + break; + } + case VIDIOC_G_CTRL: + { + retval = mxc_get_v42lout_control(vout, arg); + break; + } + case VIDIOC_S_CTRL: + { + retval = mxc_set_v42lout_control(vout, arg); + break; + } + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + cap->bounds = vout->crop_bounds[vout->cur_disp_output]; + cap->defrect = vout->crop_bounds[vout->cur_disp_output]; + retval = 0; + break; + } + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + crop->c = vout->crop_current; + break; + } + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = + &(vout->crop_bounds[vout->cur_disp_output]); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + if (crop->c.height < 0) { + retval = -EINVAL; + break; + } + if (crop->c.width < 0) { + retval = -EINVAL; + break; + } + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = + b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.top = b->left; + if (crop->c.left > b->left + b->width) + crop->c.top = b->left + b->width; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + /* stride line limitation */ + crop->c.height -= crop->c.height % 8; + crop->c.width -= crop->c.width % 8; + + vout->crop_current = crop->c; + + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height; + vout->display_buf_size *= + fmt_to_bpp(SDC_FG_FB_FORMAT) / 8; + break; + } + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if ((output->index >= 2) || + (vout->output_enabled[output->index] == false)) { + retval = -EINVAL; + break; + } + + *output = mxc_outputs[0]; + output->name[4] = '0' + output->index; + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = vout->cur_disp_output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + + if ((*p_output_num >= 2) || + (vout->output_enabled[*p_output_num] == false)) { + retval = -EINVAL; + break; + } + + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + + vout->cur_disp_output = *p_output_num; + break; + } + case VIDIOC_G_FBUF: + { + struct v4l2_framebuffer *fb = arg; + *fb = vout->v4l2_fb; + break; + } + case VIDIOC_S_FBUF: + { + struct v4l2_framebuffer *fb = arg; + vout->v4l2_fb = *fb; + vout->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + break; + } + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_PARM: + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&vout->busy_lock); + return retval; +} + +/* + * V4L2 interface - ioctl function + * + * @return None + */ +static int +mxc_v4l2out_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l2out_do_ioctl); +} + +/*! + * V4L2 interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error + */ +static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = file->private_data; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + vout_data *vout = video_get_drvdata(dev); + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + /* make buffers write-thru cacheable */ + vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) & + ~L_PTE_BUFFERABLE); + + if (remap_pfn_range(vma, start, vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_debug("mxc_mmap(V4L)i - remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + mxc_mmap_exit: + up(&vout->busy_lock); + return res; +} + +/*! + * V4L2 interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_v4l2out_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = file->private_data; + vout_data *vout = video_get_drvdata(dev); + + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + queue = &vout->v4l_bufq; + poll_wait(file, queue, wait); + + up(&vout->busy_lock); + return res; +} + +static struct file_operations mxc_v4l2out_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l2out_open, + .release = mxc_v4l2out_close, + .ioctl = mxc_v4l2out_ioctl, + .mmap = mxc_v4l2out_mmap, + .poll = mxc_v4l2out_poll, +}; + +static struct video_device mxc_v4l2out_template = { + .name = "MXC Video Output", + .vfl_type = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING, + .fops = &mxc_v4l2out_fops, + .release = video_device_release, +}; + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxc_v4l2out_probe(struct platform_device *pdev) +{ + int i; + vout_data *vout; + + /* + * Allocate sufficient memory for the fb structure + */ + g_vout = vout = kmalloc(sizeof(vout_data), GFP_KERNEL); + + if (!vout) + return 0; + + memset(vout, 0, sizeof(vout_data)); + + vout->video_dev = video_device_alloc(); + if (vout->video_dev == NULL) + return -1; + vout->video_dev->minor = -1; + + *(vout->video_dev) = mxc_v4l2out_template; + + /* register v4l device */ + if (video_register_device(vout->video_dev, + VFL_TYPE_GRABBER, video_nr) == -1) { + pr_debug("video_register_device failed\n"); + return 0; + } + pr_debug("mxc_v4l2out: registered device video%d\n", + vout->video_dev->minor & 0x1f); + + video_set_drvdata(vout->video_dev, vout); + + init_MUTEX(&vout->param_lock); + init_MUTEX(&vout->busy_lock); + + /* setup outputs and cropping */ + vout->cur_disp_output = -1; + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strncmp(idstr, "DISP", 4) == 0) { + int disp_num = i; + vout->crop_bounds[disp_num].left = 0; + vout->crop_bounds[disp_num].top = 0; + vout->crop_bounds[disp_num].width = + registered_fb[i]->var.xres; + vout->crop_bounds[disp_num].height = + registered_fb[i]->var.yres; + vout->output_enabled[disp_num] = true; + vout->output_fb_num[disp_num] = i; + if (vout->cur_disp_output == -1) + vout->cur_disp_output = disp_num; + } + + } + vout->crop_current = vout->crop_bounds[vout->cur_disp_output]; + + /* Setup framebuffer parameters */ + vout->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + vout->v4l2_fb.flags = V4L2_FBUF_FLAG_PRIMARY; + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2out_driver = { + .driver = { + .name = "MXC Video Output", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, + .probe = mxc_v4l2out_probe, + .remove = NULL, +}; + +static void camera_platform_release(struct device *device) +{ +} + +static struct platform_device mxc_v4l2out_device = { + .name = "MXC Video Output", + .dev = { + .release = camera_platform_release, + }, + .id = 0, +}; + +/*! + * mxc v4l2 init function + * + */ +static int mxc_v4l2out_init(void) +{ + u8 err = 0; + + err = platform_driver_register(&mxc_v4l2out_driver); + if (err == 0) { + platform_device_register(&mxc_v4l2out_device); + } + return err; +} + +/*! + * mxc v4l2 cleanup function + * + */ +static void mxc_v4l2out_clean(void) +{ + pr_debug("unregistering video\n"); + + video_unregister_device(g_vout->video_dev); + + platform_driver_unregister(&mxc_v4l2out_driver); + platform_device_unregister(&mxc_v4l2out_device); + kfree(g_vout); + g_vout = NULL; +} + +module_init(mxc_v4l2out_init); +module_exit(mxc_v4l2out_clean); + +module_param(video_nr, int, 0444); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2-driver for MXC video output"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c b/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c new file mode 100644 index 000000000000..4a82da2d7d00 --- /dev/null +++ b/drivers/media/video/mxc/output/mx31_v4l2_wvga_output.c @@ -0,0 +1,1926 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/output/mxc_v4l2_output.c + * + * @brief MXC V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MXC IPU Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <asm/cacheflush.h> +#include <asm/io.h> +#include <asm/semaphore.h> +#include <linux/dma-mapping.h> + +#include <mach/mxcfb.h> +#include <mach/ipu.h> + +#include "mxc_v4l2_output.h" + +vout_data *g_vout; +#define SDC_FG_FB_FORMAT IPU_PIX_FMT_RGB565 + +struct v4l2_output mxc_outputs[2] = { + { + .index = MXC_V4L2_OUT_2_SDC, + .name = "DISP3 Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN}, + { + .index = MXC_V4L2_OUT_2_ADC, + .name = "DISPx Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN} +}; + +static int video_nr = 16; +static DEFINE_SPINLOCK(g_lock); +static unsigned int g_pp_out_number; +static unsigned int g_pp_in_number; + +/* debug counters */ +uint32_t g_irq_cnt; +uint32_t g_buf_output_cnt; +uint32_t g_buf_q_cnt; +uint32_t g_buf_dq_cnt; + +static inline uint32_t channel_2_dma(ipu_channel_t ch, ipu_buffer_t type) +{ + return ((type == IPU_INPUT_BUFFER) ? ((uint32_t) ch & 0xFF) : + ((type == IPU_OUTPUT_BUFFER) ? (((uint32_t) ch >> 8) & 0xFF) + : (((uint32_t) ch >> 16) & 0xFF))); +}; + +static inline uint32_t DMAParamAddr(uint32_t dma_ch) +{ + return (0x10000 | (dma_ch << 4)); +}; + +#define QUEUE_SIZE (MAX_FRAME_NUM + 1) +static inline int queue_size(v4l_queue * q) +{ + if (q->tail >= q->head) + return (q->tail - q->head); + else + return ((q->tail + QUEUE_SIZE) - q->head); +} + +static inline int queue_buf(v4l_queue * q, int idx) +{ + if (((q->tail + 1) % QUEUE_SIZE) == q->head) + return -1; /* queue full */ + q->list[q->tail] = idx; + q->tail = (q->tail + 1) % QUEUE_SIZE; + return 0; +} + +static inline int dequeue_buf(v4l_queue * q) +{ + int ret; + if (q->tail == q->head) + return -1; /* queue empty */ + ret = q->list[q->head]; + q->head = (q->head + 1) % QUEUE_SIZE; + return ret; +} + +static inline int peek_next_buf(v4l_queue * q) +{ + if (q->tail == q->head) + return -1; /* queue empty */ + return q->list[q->head]; +} + +static inline unsigned long get_jiffies(struct timeval *t) +{ + struct timeval cur; + + if (t->tv_usec >= 1000000) { + t->tv_sec += t->tv_usec / 1000000; + t->tv_usec = t->tv_usec % 1000000; + } + + do_gettimeofday(&cur); + if ((t->tv_sec < cur.tv_sec) + || ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec))) + return jiffies; + + if (t->tv_usec < cur.tv_usec) { + cur.tv_sec = t->tv_sec - cur.tv_sec - 1; + cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec; + } else { + cur.tv_sec = t->tv_sec - cur.tv_sec; + cur.tv_usec = t->tv_usec - cur.tv_usec; + } + + return jiffies + timeval_to_jiffies(&cur); +} + +/*! + * Private function to free buffers + * + * @param bufs_paddr Array of physical address of buffers to be freed + * + * @param bufs_vaddr Array of virtual address of buffers to be freed + * + * @param num_buf Number of buffers to be freed + * + * @param size Size for each buffer to be free + * + * @return status 0 success. + */ +static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + if (bufs_vaddr[i] != 0) { + dma_free_coherent(0, size, bufs_vaddr[i], + bufs_paddr[i]); + pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]); + bufs_paddr[i] = 0; + bufs_vaddr[i] = NULL; + } + } + return 0; +} + +/*! + * Private function to allocate buffers + * + * @param bufs_paddr Output array of physical address of buffers allocated + * + * @param bufs_vaddr Output array of virtual address of buffers allocated + * + * @param num_buf Input number of buffers to allocate + * + * @param size Input size for each buffer to allocate + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + bufs_vaddr[i] = dma_alloc_coherent(0, size, + &bufs_paddr[i], + GFP_DMA | GFP_KERNEL); + + if (bufs_vaddr[i] == 0) { + mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size); + printk(KERN_ERR "dma_alloc_coherent failed.\n"); + return -ENOBUFS; + } + pr_debug("allocated @ paddr=0x%08X, size=%d.\n", + (u32) bufs_paddr[i], size); + } + + return 0; +} + +/* + * Returns bits per pixel for given pixel format + * + * @param pixelformat V4L2_PIX_FMT_RGB565, + * V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return bits per pixel of pixelformat + */ +static u32 fmt_to_bpp(u32 pixelformat) +{ + u32 bpp; + + switch (pixelformat) { + case V4L2_PIX_FMT_RGB565: + bpp = 16; + break; + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_RGB24: + bpp = 24; + break; + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_RGB32: + bpp = 32; + break; + default: + bpp = 8; + break; + } + return bpp; +} + +static u32 bpp_to_fmt(struct fb_info *fbi) +{ + if (fbi->var.nonstd) + return fbi->var.nonstd; + + if (fbi->var.bits_per_pixel == 24) + return V4L2_PIX_FMT_BGR24; + else if (fbi->var.bits_per_pixel == 32) + return V4L2_PIX_FMT_BGR32; + else if (fbi->var.bits_per_pixel == 16) + return V4L2_PIX_FMT_RGB565; + + return 0; +} + +static void mxc_v4l2out_timer_handler(unsigned long arg) +{ + int index; + unsigned long timeout; + unsigned long lock_flags = 0; + vout_data *vout = (vout_data *) arg; + + dev_dbg(vout->video_dev->dev, "timer handler: %lu\n", jiffies); + + spin_lock_irqsave(&g_lock, lock_flags); + + if ((vout->state == STATE_STREAM_STOPPING) + || (vout->state == STATE_STREAM_OFF)) + goto exit0; + /* + * If timer occurs before IPU h/w is ready, then set the state to + * paused and the timer will be set again when next buffer is queued + * or PP comletes + */ + if (vout->ipu_buf[0] != -1) { + dev_dbg(vout->video_dev->dev, "IPU buffer busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* One frame buffer should be ready here */ + if (vout->frame_count % 2 == 1) { + /* set BUF0 rdy */ + if (ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 0) < + 0) + pr_debug("error selecting display buf 0"); + } else { + if (ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 1) < + 0) + pr_debug("error selecting display buf 1"); + } + + /* Dequeue buffer and pass to IPU */ + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + dev_err(vout->video_dev->dev, + "mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit0; + } + + g_buf_dq_cnt++; + vout->frame_count++; + vout->ipu_buf[1] = vout->ipu_buf[0] = index; + + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + 0, + vout->v4l2_bufs[vout->ipu_buf[0]].m. + offset) < 0) + goto exit0; + + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + 1, + vout->v4l2_bufs[vout->ipu_buf[0]].m. + offset + vout->v2f.fmt.pix.width / 2) < 0) + goto exit0; + + /* All buffer should now ready in IPU out, tranfer to display buf */ + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, + 0, + vout-> + display_bufs[(vout->frame_count - + 1) % 2]) < 0) { + dev_err(vout->video_dev->dev, + "unable to update buffer %d address\n", + vout->next_rdy_ipu_buf); + goto exit0; + } + if (ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, + 1, + vout-> + display_bufs[(vout->frame_count - + 1) % 2] + + vout->crop_current.width / 2 * + bytes_per_pixel(SDC_FG_FB_FORMAT)) < 0) { + dev_err(vout->video_dev->dev, + "unable to update buffer %d address\n", + vout->next_rdy_ipu_buf); + goto exit0; + } + + if (ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0) < 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + goto exit0; + } + + if (ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0) < 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + goto exit0; + } + + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + if (index != -1) { + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + dev_dbg(vout->video_dev->dev, + "warning: timer timeout already expired.\n"); + } + if (mod_timer(&vout->output_timer, timeout)) + dev_dbg(vout->video_dev->dev, + "warning: timer was already set\n"); + + dev_dbg(vout->video_dev->dev, + "timer handler next schedule: %lu\n", timeout); + } else { + vout->state = STATE_STREAM_PAUSED; + } + +exit0: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +extern void _ipu_write_param_mem(uint32_t addr, uint32_t *data, + uint32_t numWords); + +static irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id) +{ + unsigned long lock_flags = 0; + vout_data *vout = dev_id; + uint32_t u_offset; + uint32_t v_offset; + uint32_t local_params[4]; + uint32_t width, height; + uint32_t dma_chan; + + spin_lock_irqsave(&g_lock, lock_flags); + g_irq_cnt++; + + dma_chan = channel_2_dma(vout->post_proc_ch, IPU_INPUT_BUFFER); + memset(&local_params, 0, sizeof(local_params)); + + if (g_pp_in_number % 2 == 1) { + u_offset = vout->offset.u_offset - vout->v2f.fmt.pix.width / 4; + v_offset = vout->offset.v_offset - vout->v2f.fmt.pix.width / 4; + width = vout->v2f.fmt.pix.width / 2; + height = vout->v2f.fmt.pix.height; + local_params[3] = + (uint32_t) ((width - 1) << 12) | ((uint32_t) (height - + 1) << 24); + local_params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32)); + local_params[2] = u_offset >> (64 - 53); + local_params[2] |= v_offset << (79 - 64); + local_params[3] |= v_offset >> (96 - 79); + _ipu_write_param_mem(DMAParamAddr(dma_chan), local_params, 4); + + if (ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 1) < + 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + } + } else { + u_offset = vout->offset.u_offset; + v_offset = vout->offset.v_offset; + width = vout->v2f.fmt.pix.width / 2; + height = vout->v2f.fmt.pix.height; + local_params[3] = + (uint32_t) ((width - 1) << 12) | ((uint32_t) (height - + 1) << 24); + local_params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32)); + local_params[2] = u_offset >> (64 - 53); + local_params[2] |= v_offset << (79 - 64); + local_params[3] |= v_offset >> (96 - 79); + _ipu_write_param_mem(DMAParamAddr(dma_chan), local_params, 4); + } + g_pp_in_number++; + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return IRQ_HANDLED; +} + +static irqreturn_t mxc_v4l2out_pp_out_irq_handler(int irq, void *dev_id) +{ + vout_data *vout = dev_id; + int index; + unsigned long timeout; + u32 lock_flags = 0; + + spin_lock_irqsave(&g_lock, lock_flags); + + if (g_pp_out_number % 2 == 1) { + if (ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 1) + < 0) { + dev_err(vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + } + } else { + if (vout->ipu_buf[0] != -1) { + vout->v4l2_bufs[vout->ipu_buf[0]].flags = + V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, vout->ipu_buf[0]); + wake_up_interruptible(&vout->v4l_bufq); + vout->ipu_buf[0] = -1; + } + if (vout->state == STATE_STREAM_STOPPING) { + if ((vout->ipu_buf[0] == -1) + && (vout->ipu_buf[1] == -1)) + vout->state = STATE_STREAM_OFF; + } else if ((vout->state == STATE_STREAM_PAUSED) + && ((index = peek_next_buf(&vout->ready_q)) != -1)) { + /*! + * Setup timer for next buffer, + * when stream has been paused + */ + pr_debug("next index %d\n", index); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index]. + timestamp); + + if (jiffies >= timeout) { + pr_debug + ("warning: timer timeout" + "already expired.\n"); + } + + vout->state = STATE_STREAM_ON; + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } + } + g_pp_out_number++; + + spin_unlock_irqrestore(&g_lock, lock_flags); + return IRQ_HANDLED; +} + +/*! + * Start the output stream + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamon(vout_data *vout) +{ + struct device *dev = vout->video_dev->dev; + ipu_channel_params_t params; + struct mxcfb_pos fb_pos; + struct fb_var_screeninfo fbvar; + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + int pp_in_buf[2]; + u16 out_width; + u16 out_height; + ipu_channel_t display_input_ch = MEM_PP_MEM; + bool use_direct_adc = false; + mm_segment_t old_fs; + + if (!vout) + return -EINVAL; + + if (vout->state != STATE_STREAM_OFF) + return -EBUSY; + + if (queue_size(&vout->ready_q) < 2) { + dev_err(dev, "2 buffers not been queued yet!\n"); + return -EINVAL; + } + + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + + vout->next_done_ipu_buf = vout->next_rdy_ipu_buf = 0; + vout->ipu_buf[0] = pp_in_buf[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf[1] = pp_in_buf[1] = vout->ipu_buf[0]; + vout->frame_count = 1; + g_pp_out_number = 1; + g_pp_in_number = 1; + + ipu_enable_irq(IPU_IRQ_PP_IN_EOF); + ipu_enable_irq(IPU_IRQ_PP_OUT_EOF); + + /* Init Display Channel */ +#ifdef CONFIG_FB_MXC_ASYNC_PANEL + if (vout->cur_disp_output < DISP3) { + mxcfb_set_refresh_mode(fbi, MXCFB_REFRESH_OFF, 0); + fbi = NULL; + if (ipu_can_rotate_in_place(vout->rotate)) { + dev_dbg(dev, "Using PP direct to ADC channel\n"); + use_direct_adc = true; + vout->display_ch = MEM_PP_ADC; + vout->post_proc_ch = MEM_PP_ADC; + + memset(¶ms, 0, sizeof(params)); + params.mem_pp_adc.in_width = vout->v2f.fmt.pix.width; + params.mem_pp_adc.in_height = vout->v2f.fmt.pix.height; + params.mem_pp_adc.in_pixel_fmt = + vout->v2f.fmt.pix.pixelformat; + params.mem_pp_adc.out_width = out_width; + params.mem_pp_adc.out_height = out_height; + params.mem_pp_adc.out_pixel_fmt = SDC_FG_FB_FORMAT; +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.mem_pp_adc.out_left = + 2 + vout->crop_current.left; +#else + params.mem_pp_adc.out_left = + 12 + vout->crop_current.left; +#endif + params.mem_pp_adc.out_top = vout->crop_current.top; + if (ipu_init_channel( + vout->post_proc_ch, ¶ms) != 0) { + dev_err(dev, "Error initializing PP chan\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + params.mem_pp_adc. + in_pixel_fmt, + params.mem_pp_adc.in_width, + params.mem_pp_adc.in_height, + vout->v2f.fmt.pix. + bytesperline / + bytes_per_pixel(params. + mem_pp_adc. + in_pixel_fmt), + vout->rotate, + vout-> + v4l2_bufs[pp_in_buf[0]].m. + offset, + vout-> + v4l2_bufs[pp_in_buf[1]].m. + offset, + vout->offset.u_offset, + vout->offset.v_offset) != + 0) { + dev_err(dev, "Error initializing PP in buf\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_adc. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, 0, 0, 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP" + "output buffer\n"); + return -EINVAL; + } + + } else { + dev_dbg(dev, "Using ADC SYS2 channel\n"); + vout->display_ch = ADC_SYS2; + vout->post_proc_ch = MEM_PP_MEM; + + if (vout->display_bufs[0]) { + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + } + + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height * + fmt_to_bpp(SDC_FG_FB_FORMAT) / 8; + mxc_allocate_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + memset(¶ms, 0, sizeof(params)); + params.adc_sys2.disp = vout->cur_disp_output; + params.adc_sys2.ch_mode = WriteTemplateNonSeq; +#ifdef CONFIG_FB_MXC_EPSON_PANEL + params.adc_sys2.out_left = 2 + vout->crop_current.left; +#else + params.adc_sys2.out_left = 12 + vout->crop_current.left; +#endif + params.adc_sys2.out_top = vout->crop_current.top; + if (ipu_init_channel(ADC_SYS2, ¶ms) < 0) + return -EINVAL; + + if (ipu_init_channel_buffer(vout->display_ch, + IPU_INPUT_BUFFER, + SDC_FG_FB_FORMAT, + out_width, out_height, + out_width, IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing SDC FG buffer\n"); + return -EINVAL; + } + } + } else +#endif + { /* Use SDC */ + dev_dbg(dev, "Using SDC channel\n"); + + fbvar = fbi->var; + if (vout->cur_disp_output == 3) { + vout->display_ch = MEM_FG_SYNC; + fbvar.bits_per_pixel = 16; + fbvar.nonstd = IPU_PIX_FMT_UYVY; + + fbvar.xres = fbvar.xres_virtual = out_width; + fbvar.yres = out_height; + fbvar.yres_virtual = out_height * 2; + } else if (vout->cur_disp_output == 5) { + vout->display_ch = MEM_DC_SYNC; + fbvar.bits_per_pixel = 16; + fbvar.nonstd = IPU_PIX_FMT_UYVY; + + fbvar.xres = fbvar.xres_virtual = out_width; + fbvar.yres = out_height; + fbvar.yres_virtual = out_height * 2; + } else { + vout->display_ch = MEM_BG_SYNC; + } + + fbvar.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbvar); + + fb_pos.x = vout->crop_current.left; + fb_pos.y = vout->crop_current.top; + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS, + (unsigned long)&fb_pos); + set_fs(old_fs); + } + + vout->display_bufs[1] = fbi->fix.smem_start; + vout->display_bufs[0] = fbi->fix.smem_start + + (fbi->fix.line_length * fbi->var.yres); + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height * fbi->var.bits_per_pixel / 8; + + vout->post_proc_ch = MEM_PP_MEM; + } + + /* Init PP */ + if (use_direct_adc == false) { + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.height; + out_height = vout->crop_current.width; + } + memset(¶ms, 0, sizeof(params)); + params.mem_pp_mem.in_width = vout->v2f.fmt.pix.width / 2; + params.mem_pp_mem.in_height = vout->v2f.fmt.pix.height; + params.mem_pp_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat; + params.mem_pp_mem.out_width = out_width / 2; + params.mem_pp_mem.out_height = out_height; + if (vout->display_ch == ADC_SYS2) + params.mem_pp_mem.out_pixel_fmt = SDC_FG_FB_FORMAT; + else + params.mem_pp_mem.out_pixel_fmt = bpp_to_fmt(fbi); + if (ipu_init_channel(vout->post_proc_ch, ¶ms) != 0) { + dev_err(dev, "Error initializing PP channel\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + params.mem_pp_mem.in_pixel_fmt, + params.mem_pp_mem.in_width, + params.mem_pp_mem.in_height, + vout->v2f.fmt.pix.bytesperline / + bytes_per_pixel(params.mem_pp_mem. + in_pixel_fmt), + IPU_ROTATE_NONE, + vout->v4l2_bufs[pp_in_buf[0]].m. + offset, + vout->v4l2_bufs[pp_in_buf[0]].m. + offset + params.mem_pp_mem.in_width, + vout->offset.u_offset, + vout->offset.v_offset) != 0) { + dev_err(dev, "Error initializing PP input buffer\n"); + return -EINVAL; + } + + if (!ipu_can_rotate_in_place(vout->rotate)) { + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + if (mxc_allocate_buffers + (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size) < 0) + return -ENOBUFS; + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing" + "PP output buffer\n"); + return -EINVAL; + } + + if (ipu_init_channel(MEM_ROT_PP_MEM, NULL) != 0) { + dev_err(dev, + "Error initializing PP ROT channel\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_INPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP ROT" + "input buffer\n"); + return -EINVAL; + } + + /* swap width and height */ + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + } + + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP" + "output buffer\n"); + return -EINVAL; + } + + if (ipu_link_channels(vout->post_proc_ch, + MEM_ROT_PP_MEM) < 0) + return -EINVAL; + + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 1); + + ipu_enable_channel(MEM_ROT_PP_MEM); + + display_input_ch = MEM_ROT_PP_MEM; + } else { + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_pp_mem. + out_pixel_fmt, + out_width / 2, + out_height, + out_width, + vout->rotate, + vout->display_bufs[0], + vout->display_bufs[0] + + + out_width / 2 * + bytes_per_pixel + (SDC_FG_FB_FORMAT), 0, + 0) != 0) { + dev_err(dev, + "Error initializing PP" + "output buffer\n"); + return -EINVAL; + } + } + if (ipu_unlink_channels( + display_input_ch, vout->display_ch) < 0) { + dev_err(dev, "Error linking ipu channels\n"); + return -EINVAL; + } + } + + vout->state = STATE_STREAM_PAUSED; + + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0); + + if (use_direct_adc == false) { + ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0); + ipu_enable_channel(vout->post_proc_ch); + + if (fbi) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_UNBLANK); + release_console_sem(); + } else { + ipu_enable_channel(vout->display_ch); + } + } else { + ipu_enable_channel(vout->post_proc_ch); + } + + vout->start_jiffies = jiffies; + dev_dbg(dev, + "streamon: start time = %lu jiffies\n", vout->start_jiffies); + + return 0; +} + +/*! + * Shut down the voutera + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamoff(vout_data *vout) +{ + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + int i, retval = 0; + unsigned long lockflag = 0; + + if (!vout) + return -EINVAL; + + if (vout->state == STATE_STREAM_OFF) + return 0; + + spin_lock_irqsave(&g_lock, lockflag); + + del_timer(&vout->output_timer); + + if (vout->state == STATE_STREAM_ON) + vout->state = STATE_STREAM_STOPPING; + + ipu_disable_irq(IPU_IRQ_PP_IN_EOF); + ipu_disable_irq(IPU_IRQ_PP_OUT_EOF); + + spin_unlock_irqrestore(&g_lock, lockflag); + + if (vout->post_proc_ch == MEM_PP_MEM) { /* SDC or ADC with Rotation */ + if (!ipu_can_rotate_in_place(vout->rotate)) { + ipu_unlink_channels(MEM_PP_MEM, MEM_ROT_PP_MEM); + ipu_unlink_channels(MEM_ROT_PP_MEM, vout->display_ch); + ipu_disable_channel(MEM_ROT_PP_MEM, true); + + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + } else { + ipu_unlink_channels(MEM_PP_MEM, vout->display_ch); + } + ipu_disable_channel(MEM_PP_MEM, true); + + if (vout->display_ch == ADC_SYS2) { + ipu_disable_channel(vout->display_ch, true); + ipu_uninit_channel(vout->display_ch); + } else { + fbi->var.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbi->var); + + if (vout->display_ch == MEM_FG_SYNC) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + release_console_sem(); + } + + vout->display_bufs[0] = 0; + vout->display_bufs[1] = 0; + } + + ipu_uninit_channel(MEM_PP_MEM); + if (!ipu_can_rotate_in_place(vout->rotate)) + ipu_uninit_channel(MEM_ROT_PP_MEM); + } else { /* ADC Direct */ + ipu_disable_channel(MEM_PP_ADC, true); + ipu_uninit_channel(MEM_PP_ADC); + } + vout->ready_q.head = vout->ready_q.tail = 0; + vout->done_q.head = vout->done_q.tail = 0; + for (i = 0; i < vout->buffer_cnt; i++) { + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + + vout->state = STATE_STREAM_OFF; + +#ifdef CONFIG_FB_MXC_ASYNC_PANEL + if (vout->cur_disp_output < DISP3) { + if (vout->display_bufs[0] != 0) { + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, 2, + vout->display_buf_size); + } + + mxcfb_set_refresh_mode(registered_fb + [vout-> + output_fb_num[vout->cur_disp_output]], + MXCFB_REFRESH_PARTIAL, 0); + } +#endif + + return retval; +} + +/* + * Valid whether the palette is supported + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 1 if supported, 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return ((palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_BGR24) || + (palette == V4L2_PIX_FMT_RGB24) || + (palette == V4L2_PIX_FMT_BGR32) || + (palette == V4L2_PIX_FMT_RGB32) || + (palette == V4L2_PIX_FMT_NV12) || + (palette == V4L2_PIX_FMT_YUV422P) || + (palette == V4L2_PIX_FMT_YUV420)); +} + +/* + * V4L2 - Handles VIDIOC_G_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_g_fmt(vout_data *vout, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + *f = vout->v2f; + return 0; +} + +/* + * V4L2 - Handles VIDIOC_S_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_s_fmt(vout_data *vout, struct v4l2_format *f) +{ + int retval = 0; + u32 size = 0; + u32 bytesperline; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + goto err0; + } + if (!valid_mode(f->fmt.pix.pixelformat)) { + dev_err(vout->video_dev->dev, "pixel format not supported\n"); + retval = -EINVAL; + goto err0; + } + + bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) / + 8; + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV422P: + /* byteperline for YUV planar formats is for + Y plane only */ + size = bytesperline * f->fmt.pix.height * 2; + break; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + size = (bytesperline * f->fmt.pix.height * 3) / 2; + break; + default: + size = bytesperline * f->fmt.pix.height; + break; + } + + /* Return the actual size of the image to the app */ + if (f->fmt.pix.sizeimage < size) + f->fmt.pix.sizeimage = size; + else + size = f->fmt.pix.sizeimage; + + vout->v2f.fmt.pix = f->fmt.pix; + if (vout->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&vout->offset, + (void *)vout->v2f.fmt.pix.priv, + sizeof(vout->offset))) { + retval = -EFAULT; + goto err0; + } + } else { + vout->offset.u_offset = 0; + vout->offset.v_offset = 0; + } + + retval = 0; +err0: + return retval; +} + +/* + * V4L2 - Handles VIDIOC_G_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42lout_control(vout_data *vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0; + case V4L2_CID_VFLIP: + return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0; + case (V4L2_CID_PRIVATE_BASE + 1): + return vout->rotate; + default: + return -EINVAL; + } +} + +/* + * V4L2 - Handles VIDIOC_S_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42lout_control(vout_data *vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + vout->rotate |= c->value ? IPU_ROTATE_HORIZ_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_VFLIP: + vout->rotate |= c->value ? IPU_ROTATE_VERT_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_MXC_ROT: + vout->rotate = c->value; + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 interface - open function + * + * @param inode structure inode * + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l2out_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + int err; + + if (!vout) + return -ENODEV; + + down(&vout->busy_lock); + + err = -EINTR; + if (signal_pending(current)) + goto oops; + + if (vout->open_count++ == 0) { + ipu_request_irq(IPU_IRQ_PP_IN_EOF, + mxc_v4l2out_pp_in_irq_handler, + 0, dev->name, vout); + ipu_request_irq(IPU_IRQ_PP_OUT_EOF, + mxc_v4l2out_pp_out_irq_handler, + 0, dev->name, vout); + + init_waitqueue_head(&vout->v4l_bufq); + + init_timer(&vout->output_timer); + vout->output_timer.function = mxc_v4l2out_timer_handler; + vout->output_timer.data = (unsigned long)vout; + + vout->state = STATE_STREAM_OFF; + vout->rotate = IPU_ROTATE_NONE; + g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0; + + } + + file->private_data = dev; + + up(&vout->busy_lock); + + return 0; + +oops: + up(&vout->busy_lock); + return err; +} + +/*! + * V4L2 interface - close function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l2out_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + if (--vout->open_count == 0) { + if (vout->state != STATE_STREAM_OFF) + mxc_v4l2out_streamoff(vout); + + ipu_free_irq(IPU_IRQ_PP_IN_EOF, vout); + ipu_free_irq(IPU_IRQ_PP_OUT_EOF, vout); + + file->private_data = NULL; + + mxc_free_buffers(vout->queue_buf_paddr, vout->queue_buf_vaddr, + vout->buffer_cnt, vout->queue_buf_size); + vout->buffer_cnt = 0; + mxc_free_buffers(vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + + /* capture off */ + wake_up_interruptible(&vout->v4l_bufq); + } + + return 0; +} + +/*! + * V4L2 interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l2out_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *vdev = file->private_data; + vout_data *vout = video_get_drvdata(vdev); + int retval = 0; + int i = 0; + + if (!vout) + return -EBADF; + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2_output"); + cap->version = 0; + cap->capabilities = + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *gf = arg; + retval = mxc_v4l2out_g_fmt(vout, gf); + break; + } + case VIDIOC_S_FMT: + { + struct v4l2_format *sf = arg; + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + retval = mxc_v4l2out_s_fmt(vout, sf); + break; + } + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (req->memory != V4L2_MEMORY_MMAP)) { + dev_dbg(vdev->dev, + "VIDIOC_REQBUFS: incorrect" + "buffer type\n"); + retval = -EINVAL; + break; + } + + if (req->count == 0) + mxc_v4l2out_streamoff(vout); + + if (vout->state == STATE_STREAM_OFF) { + if (vout->queue_buf_paddr[0] != 0) { + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + dev_dbg(vdev->dev, + "VIDIOC_REQBUFS:" + "freed buffers\n"); + } + vout->buffer_cnt = 0; + } else { + dev_dbg(vdev->dev, + "VIDIOC_REQBUFS: Buffer is in use\n"); + retval = -EBUSY; + break; + } + + if (req->count == 0) + break; + + if (req->count < MIN_FRAME_NUM) + req->count = MIN_FRAME_NUM; + else if (req->count > MAX_FRAME_NUM) + req->count = MAX_FRAME_NUM; + vout->buffer_cnt = req->count; + vout->queue_buf_size = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + + retval = mxc_allocate_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + if (retval < 0) + break; + + /* Init buffer queues */ + vout->done_q.head = 0; + vout->done_q.tail = 0; + vout->ready_q.head = 0; + vout->ready_q.tail = 0; + + for (i = 0; i < vout->buffer_cnt; i++) { + memset(&(vout->v4l2_bufs[i]), 0, + sizeof(vout->v4l2_bufs[i])); + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP; + vout->v4l2_bufs[i].index = i; + vout->v4l2_bufs[i].type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + vout->v4l2_bufs[i].length = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + vout->v4l2_bufs[i].m.offset = + (unsigned long)vout->queue_buf_paddr[i]; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + break; + } + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + u32 type = buf->type; + int index = buf->index; + + if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + dev_dbg(vdev->dev, + "VIDIOC_QUERYBUFS: incorrect" + "buffer type\n"); + retval = -EINVAL; + break; + } + down(&vout->param_lock); + memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf)); + up(&vout->param_lock); + break; + } + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int index = buf->index; + unsigned long lock_flags; + + if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + retval = -EINVAL; + break; + } + + dev_dbg(vdev->dev, "VIDIOC_QBUF: %d\n", buf->index); + + /* mmapped buffers are L1 WB cached, + * so we need to clean them */ + if (buf->flags & V4L2_BUF_FLAG_MAPPED) + flush_cache_all(); + + spin_lock_irqsave(&g_lock, lock_flags); + + memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf)); + vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED; + + g_buf_q_cnt++; + queue_buf(&vout->ready_q, index); + if (vout->state == STATE_STREAM_PAUSED) { + unsigned long timeout; + + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == + 0) + && (vout->v4l2_bufs[index].timestamp. + tv_usec == 0)) + timeout = + vout->start_jiffies + + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index]. + timestamp); + + if (jiffies >= timeout) { + dev_dbg(vout->video_dev->dev, + "warning: timer timeout" + "already expired.\n"); + } + vout->output_timer.expires = timeout; + dev_dbg(vdev->dev, + "QBUF: frame #%u timeout @" + " %lu jiffies, current = %lu\n", + vout->frame_count, timeout, jiffies); + add_timer(&vout->output_timer); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int idx; + + if ((queue_size(&vout->done_q) == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + if (!wait_event_interruptible_timeout(vout->v4l_bufq, + queue_size(&vout-> + done_q) + != 0, 10 * HZ)) { + dev_dbg(vdev->dev, "VIDIOC_DQBUF: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + dev_dbg(vdev->dev, + "VIDIOC_DQBUF: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + idx = dequeue_buf(&vout->done_q); + if (idx == -1) { /* No frame free */ + dev_dbg(vdev->dev, + "VIDIOC_DQBUF: no free buffers\n"); + retval = -EAGAIN; + break; + } + if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) == + 0) + dev_dbg(vdev->dev, + "VIDIOC_DQBUF: buffer in done q, " + "but not flagged as done\n"); + + vout->v4l2_bufs[idx].flags = 0; + memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf)); + dev_dbg(vdev->dev, "VIDIOC_DQBUF: %d\n", buf->index); + break; + } + case VIDIOC_STREAMON: + { + retval = mxc_v4l2out_streamon(vout); + break; + } + case VIDIOC_STREAMOFF: + { + retval = mxc_v4l2out_streamoff(vout); + break; + } + case VIDIOC_G_CTRL: + { + retval = mxc_get_v42lout_control(vout, arg); + break; + } + case VIDIOC_S_CTRL: + { + retval = mxc_set_v42lout_control(vout, arg); + break; + } + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + + cap->bounds = vout->crop_bounds[vout->cur_disp_output]; + cap->defrect = vout->crop_bounds[vout->cur_disp_output]; + retval = 0; + break; + } + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + crop->c = vout->crop_current; + break; + } + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = + &(vout->crop_bounds[vout->cur_disp_output]); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + if (crop->c.height < 0) { + retval = -EINVAL; + break; + } + if (crop->c.width < 0) { + retval = -EINVAL; + break; + } + + /* only full screen supported for SDC BG */ + if (vout->cur_disp_output == 4) { + crop->c = vout->crop_current; + break; + } + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top >= b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = + b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.left = b->left; + if (crop->c.left >= b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + /* stride line limitation */ + crop->c.height -= crop->c.height % 8; + crop->c.width -= crop->c.width % 8; + + vout->crop_current = crop->c; + break; + } + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if ((output->index >= 5) || + (vout->output_enabled[output->index] == false)) { + retval = -EINVAL; + break; + } + + if (output->index < 3) { + *output = mxc_outputs[MXC_V4L2_OUT_2_ADC]; + output->name[4] = '0' + output->index; + } else { + *output = mxc_outputs[MXC_V4L2_OUT_2_SDC]; + } + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = vout->cur_disp_output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + int fbnum; + struct v4l2_rect *b; + + if ((*p_output_num >= MXC_V4L2_OUT_NUM_OUTPUTS) || + (vout->output_enabled[*p_output_num] == false)) { + retval = -EINVAL; + break; + } + + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + + vout->cur_disp_output = *p_output_num; + + /* Update bounds in case they have changed */ + b = &vout->crop_bounds[vout->cur_disp_output]; + + fbnum = vout->output_fb_num[vout->cur_disp_output]; + if (vout->cur_disp_output == 3) + fbnum = vout->output_fb_num[4]; + + b->width = registered_fb[fbnum]->var.xres; + b->height = registered_fb[fbnum]->var.yres; + + vout->crop_current = *b; + break; + } + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_PARM: + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&vout->busy_lock); + return retval; +} + +/* + * V4L2 interface - ioctl function + * + * @return None + */ +static int +mxc_v4l2out_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l2out_do_ioctl); +} + +/*! + * V4L2 interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error + */ +static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *vdev = video_devdata(file); + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + int i; + vout_data *vout = video_get_drvdata(vdev); + + dev_dbg(vdev->dev, "pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + for (i = 0; i < vout->buffer_cnt; i++) { + if ((vout->v4l2_bufs[i].m.offset == + (vma->vm_pgoff << PAGE_SHIFT)) && + (vout->v4l2_bufs[i].length >= size)) { + vout->v4l2_bufs[i].flags |= V4L2_BUF_FLAG_MAPPED; + break; + } + } + if (i == vout->buffer_cnt) { + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + /* make buffers inner write-back, outer write-thru cacheable */ + vma->vm_page_prot = pgprot_outer_wrthru(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + dev_dbg(vdev->dev, "mmap remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + +mxc_mmap_exit: + up(&vout->busy_lock); + return res; +} + +/*! + * V4L2 interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_v4l2out_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + queue = &vout->v4l_bufq; + poll_wait(file, queue, wait); + + up(&vout->busy_lock); + return res; +} + +static struct +file_operations mxc_v4l2out_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l2out_open, + .release = mxc_v4l2out_close, + .ioctl = mxc_v4l2out_ioctl, + .mmap = mxc_v4l2out_mmap, + .poll = mxc_v4l2out_poll, +}; + +static struct video_device mxc_v4l2out_template = { + .owner = THIS_MODULE, + .name = "MXC Video Output", + .type = 0, + .type2 = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING, + .fops = &mxc_v4l2out_fops, + .release = video_device_release, +}; + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxc_v4l2out_probe(struct platform_device *pdev) +{ + int i; + vout_data *vout; + + /* + * Allocate sufficient memory for the fb structure + */ + g_vout = vout = kmalloc(sizeof(vout_data), GFP_KERNEL); + + if (!vout) + return 0; + + memset(vout, 0, sizeof(vout_data)); + + vout->video_dev = video_device_alloc(); + if (vout->video_dev == NULL) + return -1; + vout->video_dev->dev = &pdev->dev; + vout->video_dev->minor = -1; + + *(vout->video_dev) = mxc_v4l2out_template; + + /* register v4l device */ + if (video_register_device(vout->video_dev, + VFL_TYPE_GRABBER, video_nr) == -1) { + dev_dbg(&pdev->dev, "video_register_device failed\n"); + return 0; + } + dev_info(&pdev->dev, "Registered device video%d\n", + vout->video_dev->minor & 0x1f); + vout->video_dev->dev = &pdev->dev; + + video_set_drvdata(vout->video_dev, vout); + + init_MUTEX(&vout->param_lock); + init_MUTEX(&vout->busy_lock); + + /* setup outputs and cropping */ + vout->cur_disp_output = -1; + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strncmp(idstr, "DISP", 4) == 0) { + int disp_num = idstr[4] - '0'; + if (disp_num == 3) { + if (strcmp(idstr, "DISP3 BG - DI1") == 0) + disp_num = 5; + else if (strncmp(idstr, "DISP3 BG", 8) == 0) + disp_num = 4; + } + vout->crop_bounds[disp_num].left = 0; + vout->crop_bounds[disp_num].top = 0; + vout->crop_bounds[disp_num].width = + registered_fb[i]->var.xres; + vout->crop_bounds[disp_num].height = + registered_fb[i]->var.yres; + vout->output_enabled[disp_num] = true; + vout->output_fb_num[disp_num] = i; + if (vout->cur_disp_output == -1) + vout->cur_disp_output = disp_num; + } + + } + vout->crop_current = vout->crop_bounds[vout->cur_disp_output]; + + platform_set_drvdata(pdev, vout); + + return 0; +} + +static int mxc_v4l2out_remove(struct platform_device *pdev) +{ + vout_data *vout = platform_get_drvdata(pdev); + + if (vout->video_dev) { + if (-1 != vout->video_dev->minor) + video_unregister_device(vout->video_dev); + else + video_device_release(vout->video_dev); + vout->video_dev = NULL; + } + + platform_set_drvdata(pdev, NULL); + + kfree(vout); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2out_driver = { + .driver = { + .name = "MXC Video Output", + }, + .probe = mxc_v4l2out_probe, + .remove = mxc_v4l2out_remove, +}; + +static struct platform_device mxc_v4l2out_device = { + .name = "MXC Video Output", + .id = 0, +}; + +/*! + * mxc v4l2 init function + * + */ +static int mxc_v4l2out_init(void) +{ + u8 err = 0; + + err = platform_driver_register(&mxc_v4l2out_driver); + if (err == 0) + platform_device_register(&mxc_v4l2out_device); + return err; +} + +/*! + * mxc v4l2 cleanup function + * + */ +static void mxc_v4l2out_clean(void) +{ + video_unregister_device(g_vout->video_dev); + + platform_driver_unregister(&mxc_v4l2out_driver); + platform_device_unregister(&mxc_v4l2out_device); + kfree(g_vout); + g_vout = NULL; +} + +module_init(mxc_v4l2out_init); +module_exit(mxc_v4l2out_clean); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2-driver for MXC video output"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/output/mxc_v4l2_output.c b/drivers/media/video/mxc/output/mxc_v4l2_output.c new file mode 100644 index 000000000000..ce577c4c27d3 --- /dev/null +++ b/drivers/media/video/mxc/output/mxc_v4l2_output.c @@ -0,0 +1,2614 @@ +/* + * Copyright 2005-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file drivers/media/video/mxc/output/mxc_v4l2_output.c + * + * @brief MXC V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MXC IPU Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/semaphore.h> +#include <linux/console.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/mxcfb.h> +#include <media/v4l2-ioctl.h> +#include <asm/cacheflush.h> +#include <mach/hardware.h> + +#include "mxc_v4l2_output.h" + +#define INTERLACED_CONTENT(vout) (((cpu_is_mx51_rev(CHIP_REV_2_0) >= 1) || \ + cpu_is_mx53()) && \ + (((vout)->field_fmt == V4L2_FIELD_INTERLACED_TB) || \ + ((vout)->field_fmt == V4L2_FIELD_INTERLACED_BT))) +#define LOAD_3FIELDS(vout) ((INTERLACED_CONTENT(vout)) && \ + ((vout)->motion_sel != HIGH_MOTION)) + +struct v4l2_output mxc_outputs[1] = { + { + .index = MXC_V4L2_OUT_2_SDC, + .name = "DISP3 Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN} +}; + +static int video_nr = 16; +static spinlock_t g_lock = SPIN_LOCK_UNLOCKED; +static int last_index_n; +static unsigned int ipu_ic_out_max_width_size; +static unsigned int ipu_ic_out_max_height_size; +/* debug counters */ +uint32_t g_irq_cnt; +uint32_t g_buf_output_cnt; +uint32_t g_buf_q_cnt; +uint32_t g_buf_dq_cnt; + +#define QUEUE_SIZE (MAX_FRAME_NUM + 1) +static __inline int queue_size(v4l_queue * q) +{ + if (q->tail >= q->head) + return (q->tail - q->head); + else + return ((q->tail + QUEUE_SIZE) - q->head); +} + +static __inline int queue_buf(v4l_queue * q, int idx) +{ + if (((q->tail + 1) % QUEUE_SIZE) == q->head) + return -1; /* queue full */ + q->list[q->tail] = idx; + q->tail = (q->tail + 1) % QUEUE_SIZE; + return 0; +} + +static __inline int dequeue_buf(v4l_queue * q) +{ + int ret; + if (q->tail == q->head) + return -1; /* queue empty */ + ret = q->list[q->head]; + q->head = (q->head + 1) % QUEUE_SIZE; + return ret; +} + +static __inline int peek_next_buf(v4l_queue * q) +{ + if (q->tail == q->head) + return -1; /* queue empty */ + return q->list[q->head]; +} + +static __inline unsigned long get_jiffies(struct timeval *t) +{ + struct timeval cur; + + if (t->tv_usec >= 1000000) { + t->tv_sec += t->tv_usec / 1000000; + t->tv_usec = t->tv_usec % 1000000; + } + + do_gettimeofday(&cur); + if ((t->tv_sec < cur.tv_sec) + || ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec))) + return jiffies; + + if (t->tv_usec < cur.tv_usec) { + cur.tv_sec = t->tv_sec - cur.tv_sec - 1; + cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec; + } else { + cur.tv_sec = t->tv_sec - cur.tv_sec; + cur.tv_usec = t->tv_usec - cur.tv_usec; + } + + return jiffies + timeval_to_jiffies(&cur); +} + +/*! + * Private function to free buffers + * + * @param bufs_paddr Array of physical address of buffers to be freed + * + * @param bufs_vaddr Array of virtual address of buffers to be freed + * + * @param num_buf Number of buffers to be freed + * + * @param size Size for each buffer to be free + * + * @return status 0 success. + */ +static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + if (bufs_vaddr[i] != 0) { + dma_free_coherent(0, size, bufs_vaddr[i], + bufs_paddr[i]); + pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]); + bufs_paddr[i] = 0; + bufs_vaddr[i] = NULL; + } + } + return 0; +} + +/*! + * Private function to allocate buffers + * + * @param bufs_paddr Output array of physical address of buffers allocated + * + * @param bufs_vaddr Output array of virtual address of buffers allocated + * + * @param num_buf Input number of buffers to allocate + * + * @param size Input size for each buffer to allocate + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + bufs_vaddr[i] = dma_alloc_coherent(0, size, + &bufs_paddr[i], + GFP_DMA | GFP_KERNEL); + + if (bufs_vaddr[i] == 0) { + mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size); + printk(KERN_ERR "dma_alloc_coherent failed.\n"); + return -ENOBUFS; + } + pr_debug("allocated @ paddr=0x%08X, size=%d.\n", + (u32) bufs_paddr[i], size); + } + + return 0; +} + +/* + * Returns bits per pixel for given pixel format + * + * @param pixelformat V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return bits per pixel of pixelformat + */ +static u32 fmt_to_bpp(u32 pixelformat) +{ + u32 bpp; + + bpp = 8 * bytes_per_pixel(pixelformat); + return bpp; +} + +static bool format_is_yuv(u32 pixelformat) +{ + switch (pixelformat) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_NV12: + return true; + break; + } + return false; +} + +static u32 bpp_to_fmt(struct fb_info *fbi) +{ + if (fbi->var.nonstd) + return fbi->var.nonstd; + + if (fbi->var.bits_per_pixel == 24) + return V4L2_PIX_FMT_BGR24; + else if (fbi->var.bits_per_pixel == 32) + return V4L2_PIX_FMT_BGR32; + else if (fbi->var.bits_per_pixel == 16) + return V4L2_PIX_FMT_RGB565; + + return 0; +} + +/* + * we are using double buffer for video playback, ipu need make + * sure current buffer should not be the same buffer of next display + * one. + */ +static int select_display_buffer(vout_data *vout, int next_buf) +{ + int ret = 0; + + if (ipu_get_cur_buffer_idx(vout->display_ch, IPU_INPUT_BUFFER) + != next_buf) + ret = ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, + next_buf); + else + dev_dbg(&vout->video_dev->dev, + "display buffer not ready for select\n"); + return ret; +} + +static void setup_next_buf_timer(vout_data *vout, int index) +{ + unsigned long timeout; + + /* Setup timer for next buffer */ + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0) + && vout->start_jiffies) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + dev_dbg(&vout->video_dev->dev, + "warning: timer timeout already expired.\n"); + } + if (mod_timer(&vout->output_timer, timeout)) + dev_dbg(&vout->video_dev->dev, + "warning: timer was already set\n"); + + dev_dbg(&vout->video_dev->dev, + "timer handler next schedule: %lu\n", timeout); +} + +static int finish_previous_frame(vout_data *vout) +{ + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + mm_segment_t old_fs; + int ret = 0; + + /* make sure buf[next_done_ipu_buf] showed */ + while (ipu_check_buffer_busy(vout->display_ch, + IPU_INPUT_BUFFER, vout->next_done_ipu_buf)) { + /* wait for display frame finish */ + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + ret = fbi->fbops->fb_ioctl(fbi, MXCFB_WAIT_FOR_VSYNC, + (unsigned int)NULL); + set_fs(old_fs); + + if (ret < 0) { + /* ic_bypass need clear display buffer ready for next update*/ + ipu_clear_buffer_ready(vout->display_ch, IPU_INPUT_BUFFER, + vout->next_done_ipu_buf); + } + } + } + + return ret; +} + +static int show_current_frame(vout_data *vout) +{ + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + mm_segment_t old_fs; + int ret = 0; + + /* make sure buf[next_rdy_ipu_buf] begin to show */ + if (ipu_get_cur_buffer_idx(vout->display_ch, IPU_INPUT_BUFFER) + != vout->next_rdy_ipu_buf) { + /* wait for display frame finish */ + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + ret = fbi->fbops->fb_ioctl(fbi, MXCFB_WAIT_FOR_VSYNC, + (unsigned int)NULL); + set_fs(old_fs); + } + } + + return ret; +} + +static void timer_work_func(struct work_struct *work) +{ + vout_data *vout = + container_of(work, vout_data, timer_work); + int index, ret; + int last_buf; + unsigned long lock_flags = 0; + + finish_previous_frame(vout); + + spin_lock_irqsave(&g_lock, lock_flags); + + if (g_buf_output_cnt == 0) { + ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 1); + } else { + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + dev_err(&vout->video_dev->dev, + "mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit; + } + g_buf_dq_cnt++; + vout->frame_count++; + + vout->ipu_buf[vout->next_rdy_ipu_buf] = index; + ret = ipu_update_channel_buffer(vout->display_ch, IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index].m.offset); + ret += select_display_buffer(vout, vout->next_rdy_ipu_buf); + if (ret < 0) { + dev_err(&vout->video_dev->dev, + "unable to update buffer %d address rc=%d\n", + vout->next_rdy_ipu_buf, ret); + goto exit; + } + spin_unlock_irqrestore(&g_lock, lock_flags); + show_current_frame(vout); + spin_lock_irqsave(&g_lock, lock_flags); + vout->next_rdy_ipu_buf = !vout->next_rdy_ipu_buf; + } + + last_buf = vout->ipu_buf[vout->next_done_ipu_buf]; + if (last_buf != -1) { + g_buf_output_cnt++; + vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, last_buf); + wake_up_interruptible(&vout->v4l_bufq); + vout->ipu_buf[vout->next_done_ipu_buf] = -1; + vout->next_done_ipu_buf = !vout->next_done_ipu_buf; + } + + if (g_buf_output_cnt > 0) { + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + if (index != -1) + setup_next_buf_timer(vout, index); + else + vout->state = STATE_STREAM_PAUSED; + + if (vout->state == STATE_STREAM_STOPPING) { + if ((vout->ipu_buf[0] == -1) && (vout->ipu_buf[1] == -1)) { + vout->state = STATE_STREAM_OFF; + } + } + } +exit: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +static void mxc_v4l2out_timer_handler(unsigned long arg) +{ + int index, ret; + unsigned long lock_flags = 0; + vout_data *vout = (vout_data *) arg; + + spin_lock_irqsave(&g_lock, lock_flags); + + if ((vout->state == STATE_STREAM_STOPPING) + || (vout->state == STATE_STREAM_OFF)) + goto exit0; + + /* + * If timer occurs before IPU h/w is ready, then set the state to + * paused and the timer will be set again when next buffer is queued + * or PP comletes + */ + if (vout->ipu_buf[vout->next_rdy_ipu_buf] != -1) { + dev_dbg(&vout->video_dev->dev, "IPU buffer busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* VDI need both buffer done before update buffer? */ + if (INTERLACED_CONTENT(vout) && + (vout->ipu_buf[!vout->next_rdy_ipu_buf] != -1)) { + dev_dbg(&vout->video_dev->dev, "IPU buffer busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* Handle ic bypass mode in work queue */ + if (vout->ic_bypass) { + if (queue_work(vout->v4l_wq, &vout->timer_work) == 0) { + dev_err(&vout->video_dev->dev, "work was in queue already!\n "); + vout->state = STATE_STREAM_PAUSED; + } + goto exit0; + } + + /* Dequeue buffer and pass to IPU */ + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + dev_err(&vout->video_dev->dev, + "mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit0; + } + g_buf_dq_cnt++; + vout->frame_count++; + + /* update next buffer */ + if (LOAD_3FIELDS(vout)) { + int index_n = index; + int index_p = last_index_n; + vout->ipu_buf_p[vout->next_rdy_ipu_buf] = last_index_n; + vout->ipu_buf[vout->next_rdy_ipu_buf] = index; + vout->ipu_buf_n[vout->next_rdy_ipu_buf] = index; + ret = ipu_update_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index].m.offset); + ret += ipu_update_channel_buffer(MEM_VDI_PRP_VF_MEM_P, + IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index_p].m.offset + vout->bytesperline); + ret += ipu_update_channel_buffer(MEM_VDI_PRP_VF_MEM_N, + IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index_n].m.offset) + vout->bytesperline; + last_index_n = index; + } else { + vout->ipu_buf[vout->next_rdy_ipu_buf] = index; + if (vout->pp_split) { + vout->ipu_buf[!vout->next_rdy_ipu_buf] = index; + /* always left stripe */ + ret = ipu_update_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + 0,/* vout->next_rdy_ipu_buf,*/ + (vout->v4l2_bufs[index].m.offset) + + vout->pp_left_stripe.input_column + + vout->pp_up_stripe.input_column * vout->bytesperline); + + /* the U/V offset has to be updated inside of IDMAC */ + /* according to stripe offset */ + ret += ipu_update_channel_offset(vout->post_proc_ch, + IPU_INPUT_BUFFER, + vout->v2f.fmt.pix.pixelformat, + vout->v2f.fmt.pix.width, + vout->v2f.fmt.pix.height, + vout->bytesperline, + vout->offset.u_offset, + vout->offset.v_offset, + vout->pp_up_stripe.input_column, + vout->pp_left_stripe.input_column); + } else + ret = ipu_update_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf, + vout->v4l2_bufs[index].m.offset); + } + + if (ret < 0) { + dev_err(&vout->video_dev->dev, + "unable to update buffer %d address rc=%d\n", + vout->next_rdy_ipu_buf, ret); + goto exit0; + } + + /* set next buffer ready */ + if (LOAD_3FIELDS(vout)) + ret = ipu_select_multi_vdi_buffer(vout->next_rdy_ipu_buf); + else + ret = ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + vout->next_rdy_ipu_buf); + if (ret < 0) { + dev_err(&vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + goto exit0; + } + + /* Non IC split action */ + if (!vout->pp_split) + vout->next_rdy_ipu_buf = !vout->next_rdy_ipu_buf; + + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + if (index != -1) + setup_next_buf_timer(vout, index); + else + vout->state = STATE_STREAM_PAUSED; + + if (vout->state == STATE_STREAM_STOPPING) { + if ((vout->ipu_buf[0] == -1) && (vout->ipu_buf[1] == -1)) { + vout->state = STATE_STREAM_OFF; + } + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return; + +exit0: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +static irqreturn_t mxc_v4l2out_work_irq_handler(int irq, void *dev_id) +{ + int last_buf; + int index; + unsigned long lock_flags = 0; + vout_data *vout = dev_id; + int pp_out_buf_left_right = 0; + int disp_buf_num = 0; + int disp_buf_num_next = 1; + int local_buffer = 0; + int pp_out_buf_offset = 0; + int pp_out_buf_up_down = 0; + int release_buffer = 0; + u32 eba_offset = 0; + u32 vertical_offset = 0; + u16 x_pos; + u16 y_pos; + int ret = -1; + + spin_lock_irqsave(&g_lock, lock_flags); + + g_irq_cnt++; + + /* Process previous buffer */ + if (LOAD_3FIELDS(vout)) + last_buf = vout->ipu_buf_p[vout->next_done_ipu_buf]; + else + last_buf = vout->ipu_buf[vout->next_done_ipu_buf]; + + if (last_buf != -1) { + /* If IC split mode on, update output buffer number */ + if (vout->pp_split) { + pp_out_buf_up_down = vout->pp_split_buf_num & 1;/* left/right stripe */ + pp_out_buf_left_right = (vout->pp_split_buf_num >> 1) & 1; /* up/down */ + local_buffer = (vout->pp_split == 1) ? pp_out_buf_up_down : + pp_out_buf_left_right; + disp_buf_num = vout->pp_split_buf_num >> 2; + disp_buf_num_next = + ((vout->pp_split_buf_num + (vout->pp_split << 0x1)) & 7) >> 2; + if ((!pp_out_buf_left_right) || + ((!pp_out_buf_up_down) && (vout->pp_split == 1))) { + if (vout->pp_split == 1) { + eba_offset = ((pp_out_buf_left_right + pp_out_buf_up_down) & 1) ? + vout->pp_right_stripe.input_column : + vout->pp_left_stripe.input_column; + vertical_offset = pp_out_buf_up_down ? + vout->pp_up_stripe.input_column : + vout->pp_down_stripe.input_column; + + } else { + eba_offset = pp_out_buf_left_right ? + vout->pp_left_stripe.input_column : + vout->pp_right_stripe.input_column; + vertical_offset = pp_out_buf_left_right ? + vout->pp_up_stripe.input_column : + vout->pp_down_stripe.input_column; + } + + ret = ipu_update_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + (1 - local_buffer), + (vout->v4l2_bufs[vout->ipu_buf[disp_buf_num]].m.offset) + + eba_offset + vertical_offset * vout->bytesperline); + ret += ipu_update_channel_offset(vout->post_proc_ch, + IPU_INPUT_BUFFER, + vout->v2f.fmt.pix.pixelformat, + vout->v2f.fmt.pix.width, + vout->v2f.fmt.pix.height, + vout->bytesperline, + vout->offset.u_offset, + vout->offset.v_offset, + vertical_offset, + eba_offset); + + /* select right stripe */ + ret += ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + (1 - local_buffer)); + if (ret < 0) + dev_err(&vout->video_dev->dev, + "unable to set IPU buffer ready\n"); + vout->next_rdy_ipu_buf = !vout->next_rdy_ipu_buf; + + } else {/* last stripe is done, run display refresh */ + select_display_buffer(vout, disp_buf_num); + vout->ipu_buf[vout->next_done_ipu_buf] = -1; + vout->next_done_ipu_buf = !vout->next_done_ipu_buf; + vout->next_rdy_ipu_buf = !vout->next_rdy_ipu_buf; + } + + /* offset for next buffer's EBA */ + eba_offset = 0; + if (vout->pp_split == 1) { + pp_out_buf_offset = ((vout->pp_split_buf_num >> 1) & 1) ? + vout->pp_left_stripe.output_column : + vout->pp_right_stripe.output_column; + + eba_offset = ((vout->pp_split_buf_num & 1) ? + vout->pp_down_stripe.output_column : + vout->pp_up_stripe.output_column); + + } else { + pp_out_buf_offset = ((vout->pp_split_buf_num >> 1) & 1) ? + vout->pp_right_stripe.output_column : + vout->pp_left_stripe.output_column; + eba_offset = ((vout->pp_split_buf_num >> 1) & 1) ? + vout->pp_down_stripe.output_column : + vout->pp_up_stripe.output_column; + } + + if (vout->cur_disp_output == 5) { + x_pos = (vout->crop_current.left / 8) * 8; + y_pos = vout->crop_current.top; + eba_offset += (vout->xres * y_pos + x_pos) * vout->bpp / 8; + } + + + /* next buffer update */ + eba_offset = vout->display_bufs[disp_buf_num_next] + + pp_out_buf_offset + eba_offset; + + ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, + local_buffer, eba_offset); + + /* next buffer ready */ + ret = ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, local_buffer); + + /* next stripe_buffer index 0..7 */ + vout->pp_split_buf_num = (vout->pp_split_buf_num + vout->pp_split) & 0x7; + + + } else { + /* show to display */ + select_display_buffer(vout, vout->next_done_ipu_buf); + ret += ipu_select_buffer(vout->display_input_ch, IPU_OUTPUT_BUFFER, + vout->next_done_ipu_buf); + } + + /* release buffer. For split mode: if second stripe is done */ + release_buffer = vout->pp_split ? (!(vout->pp_split_buf_num & 0x3)) : 1; + if (release_buffer) { + g_buf_output_cnt++; + vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, last_buf); + wake_up_interruptible(&vout->v4l_bufq); + vout->ipu_buf[vout->next_done_ipu_buf] = -1; + if (LOAD_3FIELDS(vout)) { + vout->ipu_buf_p[vout->next_done_ipu_buf] = -1; + vout->ipu_buf_n[vout->next_done_ipu_buf] = -1; + } + vout->next_done_ipu_buf = !vout->next_done_ipu_buf; + } + } /* end of last_buf != -1 */ + + if (vout->state == STATE_STREAM_STOPPING) { + if ((vout->ipu_buf[0] == -1) && (vout->ipu_buf[1] == -1)) { + vout->state = STATE_STREAM_OFF; + } + } else if ((vout->state == STATE_STREAM_PAUSED) + && ((index = peek_next_buf(&vout->ready_q)) != -1)) { + /* Setup timer for next buffer, when stream has been paused */ + pr_debug("next index %d\n", index); + setup_next_buf_timer(vout, index); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return IRQ_HANDLED; +} + +/*! + * Initialize VDI channels + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int init_VDI_channel(vout_data *vout, ipu_channel_params_t params) +{ + struct device *dev = &vout->video_dev->dev; + + if (ipu_init_channel(MEM_VDI_PRP_VF_MEM, ¶ms) != 0) { + dev_dbg(dev, "Error initializing VDI current channel\n"); + return -EINVAL; + } + if (LOAD_3FIELDS(vout)) { + if (ipu_init_channel(MEM_VDI_PRP_VF_MEM_P, ¶ms) != 0) { + dev_err(dev, "Error initializing VDI previous channel\n"); + return -EINVAL; + } + if (ipu_init_channel(MEM_VDI_PRP_VF_MEM_N, ¶ms) != 0) { + dev_err(dev, "Error initializing VDI next channel\n"); + return -EINVAL; + } + } + return 0; +} + +/*! + * Initialize VDI channel buffers + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int init_VDI_in_channel_buffer(vout_data *vout, uint32_t in_pixel_fmt, + uint16_t in_width, uint16_t in_height, + uint32_t stride, + uint32_t u_offset, uint32_t v_offset) +{ + struct device *dev = &vout->video_dev->dev; + + if (ipu_init_channel_buffer(MEM_VDI_PRP_VF_MEM, IPU_INPUT_BUFFER, + in_pixel_fmt, in_width, in_height, stride, + IPU_ROTATE_NONE, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset, + u_offset, v_offset) != 0) { + dev_err(dev, "Error initializing VDI current input buffer\n"); + return -EINVAL; + } + if (LOAD_3FIELDS(vout)) { + if (ipu_init_channel_buffer(MEM_VDI_PRP_VF_MEM_P, + IPU_INPUT_BUFFER, + in_pixel_fmt, in_width, in_height, + stride, IPU_ROTATE_NONE, + vout->v4l2_bufs[vout->ipu_buf_p[0]].m.offset+vout->bytesperline, + vout->v4l2_bufs[vout->ipu_buf_p[0]].m.offset+vout->bytesperline, + u_offset, v_offset) != 0) { + dev_err(dev, "Error initializing VDI previous input buffer\n"); + return -EINVAL; + } + if (ipu_init_channel_buffer(MEM_VDI_PRP_VF_MEM_N, + IPU_INPUT_BUFFER, + in_pixel_fmt, in_width, in_height, + stride, IPU_ROTATE_NONE, + vout->v4l2_bufs[vout->ipu_buf_n[0]].m.offset+vout->bytesperline, + vout->v4l2_bufs[vout->ipu_buf_n[0]].m.offset+vout->bytesperline, + u_offset, v_offset) != 0) { + dev_err(dev, "Error initializing VDI next input buffer\n"); + return -EINVAL; + } + } + return 0; +} + +/*! + * Initialize VDI path + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int init_VDI(ipu_channel_params_t params, vout_data *vout, + struct device *dev, struct fb_info *fbi, + u16 out_width, u16 out_height) +{ + params.mem_prp_vf_mem.in_width = vout->v2f.fmt.pix.width; + params.mem_prp_vf_mem.in_height = vout->v2f.fmt.pix.height; + params.mem_prp_vf_mem.motion_sel = vout->motion_sel; + params.mem_prp_vf_mem.field_fmt = vout->field_fmt; + params.mem_prp_vf_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat; + params.mem_prp_vf_mem.out_width = out_width; + params.mem_prp_vf_mem.out_height = out_height; + params.mem_prp_vf_mem.out_pixel_fmt = bpp_to_fmt(fbi); + + if (init_VDI_channel(vout, params) != 0) { + dev_err(dev, "Error init_VDI_channel channel\n"); + return -EINVAL; + } + + if (init_VDI_in_channel_buffer(vout, + params.mem_prp_vf_mem.in_pixel_fmt, + params.mem_prp_vf_mem.in_width, + params.mem_prp_vf_mem.in_height, + bytes_per_pixel(params.mem_prp_vf_mem. + in_pixel_fmt), + vout->offset.u_offset, + vout->offset.v_offset) != 0) { + return -EINVAL; + } + + if (!ipu_can_rotate_in_place(vout->rotate)) { + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + if (mxc_allocate_buffers + (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size) < 0) { + return -ENOBUFS; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_prp_vf_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, 0) != 0) { + dev_err(dev, "Error initializing PRP output buffer\n"); + return -EINVAL; + } + + if (ipu_init_channel(MEM_ROT_VF_MEM, NULL) != 0) { + dev_err(dev, "Error initializing PP ROT channel\n"); + return -EINVAL; + } + if (ipu_init_channel_buffer(MEM_ROT_VF_MEM, + IPU_INPUT_BUFFER, + params.mem_prp_vf_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, 0) != 0) { + dev_err(dev, + "Error initializing PP ROT input buffer\n"); + return -EINVAL; + } + + /* swap width and height */ + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + } + + if (ipu_init_channel_buffer(MEM_ROT_VF_MEM, + IPU_OUTPUT_BUFFER, + params.mem_prp_vf_mem. + out_pixel_fmt, out_width, + out_height, out_width, + IPU_ROTATE_NONE, + vout->display_bufs[0], + vout->display_bufs[1], 0, 0) != 0) { + dev_err(dev, + "Error initializing PP-VDI output buffer\n"); + return -EINVAL; + } + + if (ipu_link_channels(vout->post_proc_ch, MEM_ROT_VF_MEM) < 0) + return -EINVAL; + + vout->display_input_ch = MEM_ROT_VF_MEM; + ipu_enable_channel(MEM_ROT_VF_MEM); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_VF_MEM, IPU_OUTPUT_BUFFER, 1); + } else { + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params.mem_prp_vf_mem. + out_pixel_fmt, out_width, + out_height, out_width, + vout->rotate, + vout->display_bufs[0], + vout->display_bufs[1], 0, 0) != 0) { + dev_err(dev, + "Error initializing PP-VDI output buffer\n"); + return -EINVAL; + } + } + return 0; +} + +/*! + * Initialize PP path + * + * @param params structure ipu_channel_params_t + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int init_PP(ipu_channel_params_t *params, vout_data *vout, + struct device *dev, struct fb_info *fbi, + u16 out_width, u16 out_height) +{ + u16 in_width, out_stride; /* stride of output channel */ + u32 eba_offset; + u16 x_pos; + u16 y_pos; + eba_offset = 0; + x_pos = 0; + y_pos = 0; + + params->mem_pp_mem.out_pixel_fmt = bpp_to_fmt(fbi); + + if (vout->cur_disp_output == 5) { + x_pos = (vout->crop_current.left / 8) * 8; + y_pos = vout->crop_current.top; + eba_offset = (vout->xres*y_pos + x_pos) * + bytes_per_pixel(params->mem_pp_mem.out_pixel_fmt); + } + + vout->bpp = fmt_to_bpp(params->mem_pp_mem.out_pixel_fmt); + out_stride = vout->xres * + bytes_per_pixel(params->mem_pp_mem.out_pixel_fmt); + in_width = params->mem_pp_mem.in_width = vout->v2f.fmt.pix.width; + params->mem_pp_mem.in_height = vout->v2f.fmt.pix.height; + params->mem_pp_mem.in_pixel_fmt = vout->v2f.fmt.pix.pixelformat; + params->mem_pp_mem.out_width = out_width; + params->mem_pp_mem.out_height = out_height; + params->mem_pp_mem.outh_resize_ratio = 0; /* 0 means unused */ + params->mem_pp_mem.outv_resize_ratio = 0; /* 0 means unused */ + /* split IC by two stripes, the by pass is impossible*/ + if (vout->pp_split) { + vout->pp_left_stripe.input_column = 0; + vout->pp_left_stripe.output_column = 0; + vout->pp_right_stripe.input_column = 0; + vout->pp_right_stripe.output_column = 0; + vout->pp_up_stripe.input_column = 0; + vout->pp_up_stripe.output_column = 0; + vout->pp_down_stripe.input_column = 0; + vout->pp_down_stripe.output_column = 0; + if (vout->pp_split != 3) { + ipu_calc_stripes_sizes( + params->mem_pp_mem.in_width, /* input frame width;>1 */ + params->mem_pp_mem.out_width, /* output frame width; >1 */ + ipu_ic_out_max_width_size, + (((unsigned long long)1) << 32), /* 32bit for fractional*/ + 1, /* equal stripes */ + params->mem_pp_mem.in_pixel_fmt, + params->mem_pp_mem.out_pixel_fmt, + &(vout->pp_left_stripe), + &(vout->pp_right_stripe)); + + vout->pp_left_stripe.input_column = vout->pp_left_stripe.input_column * + fmt_to_bpp(vout->v2f.fmt.pix.pixelformat) / 8; + vout->pp_left_stripe.output_column = vout->pp_left_stripe.output_column * + fmt_to_bpp(params->mem_pp_mem.out_pixel_fmt) / 8; + vout->pp_right_stripe.input_column = vout->pp_right_stripe.input_column * + fmt_to_bpp(vout->v2f.fmt.pix.pixelformat) / 8; + vout->pp_right_stripe.output_column = vout->pp_right_stripe.output_column * + fmt_to_bpp(params->mem_pp_mem.out_pixel_fmt) / 8; + + + /* updare parameters */ + params->mem_pp_mem.in_width = vout->pp_left_stripe.input_width; + params->mem_pp_mem.out_width = vout->pp_left_stripe.output_width; + out_width = vout->pp_left_stripe.output_width; + /* for using in ic_init*/ + params->mem_pp_mem.outh_resize_ratio = vout->pp_left_stripe.irr; + } + if (vout->pp_split != 2) { + ipu_calc_stripes_sizes( + params->mem_pp_mem.in_height, /* input frame width;>1 */ + params->mem_pp_mem.out_height, /* output frame width; >1 */ + ipu_ic_out_max_height_size, + (((unsigned long long)1) << 32),/* 32bit for fractional */ + 1, /* equal stripes */ + params->mem_pp_mem.in_pixel_fmt, + params->mem_pp_mem.out_pixel_fmt, + &(vout->pp_up_stripe), + &(vout->pp_down_stripe)); + vout->pp_down_stripe.output_column = vout->pp_down_stripe.output_column * out_stride; + vout->pp_up_stripe.output_column = vout->pp_up_stripe.output_column * out_stride; + params->mem_pp_mem.outv_resize_ratio = vout->pp_up_stripe.irr; + params->mem_pp_mem.in_height = vout->pp_up_stripe.input_width;/*height*/ + out_height = vout->pp_up_stripe.output_width;/*height*/ + if (vout->pp_split == 3) + vout->pp_split = 2;/*2 vertical stripe as two horizontal stripes */ + } + vout->pp_split_buf_num = 0; + } + + if (ipu_init_channel(vout->post_proc_ch, params) != 0) { + dev_err(dev, "Error initializing PP channel\n"); + return -EINVAL; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_INPUT_BUFFER, + params->mem_pp_mem.in_pixel_fmt, + params->mem_pp_mem.in_width, + params->mem_pp_mem.in_height, + vout->v2f.fmt.pix.bytesperline / + bytes_per_pixel(params->mem_pp_mem. + in_pixel_fmt), + IPU_ROTATE_NONE, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset, + vout->v4l2_bufs[vout->ipu_buf[1]].m.offset, + vout->offset.u_offset, + vout->offset.v_offset) != 0) { + dev_err(dev, "Error initializing PP input buffer\n"); + return -EINVAL; + } + + if (!ipu_can_rotate_in_place(vout->rotate)) { + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + if (mxc_allocate_buffers + (vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size) < 0) { + return -ENOBUFS; + } + + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params->mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_stride, + IPU_ROTATE_NONE, + vout->rot_pp_bufs[0] + eba_offset, + vout->rot_pp_bufs[1] + eba_offset, 0, 0) != 0) { + dev_err(dev, "Error initializing PP output buffer\n"); + return -EINVAL; + } + + if (ipu_init_channel(MEM_ROT_PP_MEM, NULL) != 0) { + dev_err(dev, "Error initializing PP ROT channel\n"); + return -EINVAL; + } + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_INPUT_BUFFER, + params->mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_stride, + vout->rotate, + vout->rot_pp_bufs[0], + vout->rot_pp_bufs[1], 0, 0) != 0) { + dev_err(dev, + "Error initializing PP ROT input buffer\n"); + return -EINVAL; + } + + /* swap width and height */ + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + } + + if (ipu_init_channel_buffer(MEM_ROT_PP_MEM, + IPU_OUTPUT_BUFFER, + params->mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_stride, + IPU_ROTATE_NONE, + vout->display_bufs[0] + eba_offset, + vout->display_bufs[1] + eba_offset, 0, 0) != 0) { + dev_err(dev, "Error initializing PP output buffer\n"); + return -EINVAL; + } + + if (ipu_link_channels(vout->post_proc_ch, MEM_ROT_PP_MEM) < 0) + return -EINVAL; + + vout->display_input_ch = MEM_ROT_PP_MEM; + ipu_enable_channel(MEM_ROT_PP_MEM); + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_ROT_PP_MEM, IPU_OUTPUT_BUFFER, 1); + } else { + if (ipu_init_channel_buffer(vout->post_proc_ch, + IPU_OUTPUT_BUFFER, + params->mem_pp_mem. + out_pixel_fmt, out_width, + out_height, out_stride, + vout->rotate, + vout->display_bufs[0] + eba_offset, + vout->display_bufs[1] + eba_offset, 0, 0) != 0) { + dev_err(dev, "Error initializing PP output buffer\n"); + return -EINVAL; + } + } + + /* fix EBAs for IDMAC channels */ + if (vout->pp_split) { + ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + 0, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset + + vout->pp_left_stripe.input_column + + vout->pp_up_stripe.input_column * vout->bytesperline); + + + ipu_update_channel_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, + 1, + vout->v4l2_bufs[vout->ipu_buf[0]].m.offset + + vout->pp_right_stripe.input_column + + vout->pp_up_stripe.input_column * vout->bytesperline); + + ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, + 0, + vout->display_bufs[0] + eba_offset + + vout->pp_left_stripe.output_column + + vout->pp_up_stripe.output_column); + + ipu_update_channel_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, + 1, + vout->display_bufs[0] + eba_offset + + vout->pp_right_stripe.output_column + + vout->pp_up_stripe.output_column); + } + + return 0; +} + +/*! + * Start the output stream + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamon(vout_data * vout) +{ + struct device *dev = &vout->video_dev->dev; + ipu_channel_params_t params; + struct mxcfb_pos fb_pos; + struct fb_var_screeninfo fbvar; + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + u16 out_width; + u16 out_height; + mm_segment_t old_fs; + unsigned int ipu_ch = CHAN_NONE; + int rc = 0; + + dev_dbg(dev, "mxc_v4l2out_streamon: field format=%d\n", + vout->field_fmt); + + if (!vout) + return -EINVAL; + + if (vout->state != STATE_STREAM_OFF) + return -EBUSY; + + if (queue_size(&vout->ready_q) < 2) { + dev_err(dev, "2 buffers not been queued yet!\n"); + return -EINVAL; + } + + if ((vout->field_fmt == V4L2_FIELD_BOTTOM) || (vout->field_fmt == V4L2_FIELD_TOP)) { + dev_err(dev, "4 queued buffers need, not supported yet!\n"); + return -EINVAL; + } + + /* + * params init, check whether operation exceed the IC limitation: + * whether split mode used ( ipu version >= ipuv3 only) + */ + g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0; + out_width = vout->crop_current.width; + out_height = vout->crop_current.height; + vout->next_done_ipu_buf = 0; + vout->next_rdy_ipu_buf = 1; + vout->pp_split = 0; + ipu_ic_out_max_height_size = 1024; +#ifdef CONFIG_MXC_IPU_V1 + if (cpu_is_mx35()) + ipu_ic_out_max_width_size = 800; + else + ipu_ic_out_max_width_size = 720; +#else + ipu_ic_out_max_width_size = 1024; +#endif + if ((out_width > ipu_ic_out_max_width_size) || + (out_height > ipu_ic_out_max_height_size)) + vout->pp_split = 4; + if (!INTERLACED_CONTENT(vout)) { + vout->next_done_ipu_buf = vout->next_rdy_ipu_buf = 0; + vout->ipu_buf[0] = dequeue_buf(&vout->ready_q); + /* split IC by two stripes, the by pass is impossible*/ + if ((out_width != vout->v2f.fmt.pix.width || + out_height != vout->v2f.fmt.pix.height) && + vout->pp_split) { + vout->ipu_buf[1] = vout->ipu_buf[0]; + vout->frame_count = 1; + if ((out_width > ipu_ic_out_max_width_size) && + (out_height > ipu_ic_out_max_height_size)) + vout->pp_split = 1; /*4 stripes*/ + else if (!(out_height > ipu_ic_out_max_height_size)) + vout->pp_split = 2; /*two horizontal stripes */ + else + vout->pp_split = 3; /*2 vertical stripes*/ + } else { + vout->ipu_buf[1] = dequeue_buf(&vout->ready_q); + vout->frame_count = 2; + } + } else if (!LOAD_3FIELDS(vout)) { + vout->ipu_buf[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf[1] = -1; + vout->frame_count = 1; + } else { + vout->ipu_buf_p[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf[0] = dequeue_buf(&vout->ready_q); + vout->ipu_buf_n[0] = vout->ipu_buf[0]; + vout->ipu_buf_p[1] = -1; + vout->ipu_buf[1] = -1; + vout->ipu_buf_n[1] = -1; + last_index_n = vout->ipu_buf_n[0]; + vout->frame_count = 2; + } + + /* + * Bypass IC if resizing and rotation are not needed + * Meanwhile, apply IC bypass to SDC only + */ + fbvar = fbi->var; + vout->xres = fbvar.xres; + vout->yres = fbvar.yres; + + if (vout->cur_disp_output == 3 || vout->cur_disp_output == 5) { + fbvar.bits_per_pixel = 16; + if (format_is_yuv(vout->v2f.fmt.pix.pixelformat)) + fbvar.nonstd = IPU_PIX_FMT_UYVY; + else + fbvar.nonstd = 0; + if (vout->cur_disp_output == 3) { + fbvar.xres = out_width; + fbvar.yres = out_height; + vout->xres = fbvar.xres; + vout->yres = fbvar.yres; + } + + fbvar.xres_virtual = fbvar.xres; + fbvar.yres_virtual = fbvar.yres * 2; + } + + if (out_width == vout->v2f.fmt.pix.width && + out_height == vout->v2f.fmt.pix.height && + vout->xres == out_width && + vout->yres == out_height && + ipu_can_rotate_in_place(vout->rotate) && + (vout->bytesperline == + bytes_per_pixel(vout->v2f.fmt.pix.pixelformat) * out_width) && + !INTERLACED_CONTENT(vout)) { + vout->ic_bypass = 1; + } else { + vout->ic_bypass = 0; + } + +#ifdef CONFIG_MXC_IPU_V1 + /* IPUv1 needs IC to do CSC */ + if (format_is_yuv(vout->v2f.fmt.pix.pixelformat) != + format_is_yuv(bpp_to_fmt(fbi))) + vout->ic_bypass = 0; +#endif + + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_GET_FB_IPU_CHAN, + (unsigned long)&ipu_ch); + set_fs(old_fs); + } + + if (ipu_ch == CHAN_NONE) { + dev_err(dev, "Can not get display ipu channel\n"); + return -EINVAL; + } + + vout->display_ch = ipu_ch; + + if (vout->ic_bypass) { + pr_debug("Bypassing IC\n"); + vout->pp_split = 0; + switch (vout->v2f.fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_NV12: + fbvar.bits_per_pixel = 12; + break; + case V4L2_PIX_FMT_YUV422P: + fbvar.bits_per_pixel = 16; + break; + default: + fbvar.bits_per_pixel = 8* + bytes_per_pixel(vout->v2f.fmt.pix.pixelformat); + } + fbvar.nonstd = vout->v2f.fmt.pix.pixelformat; + } + + /* Init display channel through fb API */ + fbvar.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbvar); + + if (fbi->fbops->fb_ioctl && vout->display_ch == MEM_FG_SYNC) { + fb_pos.x = vout->crop_current.left; + fb_pos.y = vout->crop_current.top; + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS, + (unsigned long)&fb_pos); + set_fs(old_fs); + } + + vout->display_bufs[1] = fbi->fix.smem_start; + vout->display_bufs[0] = fbi->fix.smem_start + + (fbi->fix.line_length * vout->yres); + vout->display_buf_size = vout->xres * + vout->yres * fbi->var.bits_per_pixel / 8; + + /* fill black color for init fb, we assume fb has double buffer*/ + if (format_is_yuv(vout->v2f.fmt.pix.pixelformat)) { + int i; + + if ((vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_UYVY) || + (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) || + (!vout->ic_bypass)) { + short * tmp = (short *) fbi->screen_base; + short color; + if (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) + color = 0x8000; + else + color = 0x80; + for (i = 0; i < (fbi->fix.line_length * fbi->var.yres_virtual)/2; + i++, tmp++) + *tmp = color; + } else if ((vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) || + (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YVU420) || + (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12)) { + char * base = (char *)fbi->screen_base; + int j, screen_size = fbi->var.xres * fbi->var.yres; + + for (j = 0; j < 2; j++) { + memset(base, 0, screen_size); + base += screen_size; + for (i = 0; i < screen_size/2; i++, base++) + *base = 0x80; + } + } else if (vout->v2f.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) { + char * base = (char *)fbi->screen_base; + int j, screen_size = fbi->var.xres * fbi->var.yres; + + for (j = 0; j < 2; j++) { + memset(base, 0, screen_size); + base += screen_size; + for (i = 0; i < screen_size; i++, base++) + *base = 0x80; + } + } + } else + memset(fbi->screen_base, 0x0, + fbi->fix.line_length * fbi->var.yres_virtual); + + if (INTERLACED_CONTENT(vout)) + vout->post_proc_ch = MEM_VDI_PRP_VF_MEM; + else if (!vout->ic_bypass) + vout->post_proc_ch = MEM_PP_MEM; + + /* Init IC channel */ + if (!vout->ic_bypass) { + if (vout->rotate >= IPU_ROTATE_90_RIGHT) { + out_width = vout->crop_current.height; + out_height = vout->crop_current.width; + } + vout->display_input_ch = vout->post_proc_ch; + memset(¶ms, 0, sizeof(params)); + if (INTERLACED_CONTENT(vout)) { + if (vout->pp_split) { + dev_err(&vout->video_dev->dev, "VDI split has not supported yet.\n"); + return -1; + } else + rc = init_VDI(params, vout, dev, fbi, out_width, out_height); + } else { + rc = init_PP(¶ms, vout, dev, fbi, out_width, out_height); + } + if (rc < 0) + return rc; + } + + if (!vout->ic_bypass) { + switch (vout->display_input_ch) { + case MEM_PP_MEM: + vout->work_irq = IPU_IRQ_PP_OUT_EOF; + break; + case MEM_VDI_PRP_VF_MEM: + vout->work_irq = IPU_IRQ_PRP_VF_OUT_EOF; + break; + case MEM_ROT_VF_MEM: + vout->work_irq = IPU_IRQ_PRP_VF_ROT_OUT_EOF; + break; + case MEM_ROT_PP_MEM: + vout->work_irq = IPU_IRQ_PP_ROT_OUT_EOF; + break; + default: + dev_err(&vout->video_dev->dev, + "not support channel, should not be here\n"); + } + } else + vout->work_irq = -1; + + if (!vout->ic_bypass && (vout->work_irq > 0)) { + ipu_clear_irq(vout->work_irq); + ipu_request_irq(vout->work_irq, + mxc_v4l2out_work_irq_handler, + 0, vout->video_dev->name, vout); + } + + vout->state = STATE_STREAM_PAUSED; + + /* Enable display and IC channels */ + if (fbi) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_UNBLANK); + release_console_sem(); + } else { + ipu_enable_channel(vout->display_ch); + } + if (!vout->ic_bypass) { +#ifndef CONFIG_MXC_IPU_V1 + ipu_enable_channel(vout->post_proc_ch); +#endif + if (LOAD_3FIELDS(vout)) { + ipu_enable_channel(MEM_VDI_PRP_VF_MEM_P); + ipu_enable_channel(MEM_VDI_PRP_VF_MEM_N); + ipu_select_multi_vdi_buffer(0); + } else if (INTERLACED_CONTENT(vout)) { + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0); + } else { + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 0); + if (!vout->pp_split) + ipu_select_buffer(vout->post_proc_ch, IPU_INPUT_BUFFER, 1); + } + ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(vout->post_proc_ch, IPU_OUTPUT_BUFFER, 1); +#ifdef CONFIG_MXC_IPU_V1 + ipu_enable_channel(vout->post_proc_ch); +#endif + } else { + ipu_update_channel_buffer(vout->display_ch, + IPU_INPUT_BUFFER, + 0, vout->v4l2_bufs[vout->ipu_buf[0]].m.offset); + ipu_update_channel_buffer(vout->display_ch, + IPU_INPUT_BUFFER, + 1, vout->v4l2_bufs[vout->ipu_buf[1]].m.offset); + if (vout->offset.u_offset || vout->offset.v_offset) + /* only update u/v offset */ + ipu_update_channel_offset(vout->display_ch, + IPU_INPUT_BUFFER, + vout->v2f.fmt.pix.pixelformat, + vout->v2f.fmt.pix.width, + vout->v2f.fmt.pix.height, + vout->bytesperline, + vout->offset.u_offset, + vout->offset.v_offset, + 0, + 0); + ipu_select_buffer(vout->display_ch, IPU_INPUT_BUFFER, 0); + queue_work(vout->v4l_wq, &vout->timer_work); + } + + vout->start_jiffies = jiffies; + + msleep(1); + + dev_dbg(dev, + "streamon: start time = %lu jiffies\n", vout->start_jiffies); + + return 0; +} + +/*! + * Shut down the voutera + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamoff(vout_data * vout) +{ + struct fb_info *fbi = + registered_fb[vout->output_fb_num[vout->cur_disp_output]]; + int i, retval = 0; + unsigned long lockflag = 0; + + if (!vout) + return -EINVAL; + + if (vout->state == STATE_STREAM_OFF) { + return 0; + } + + if (!vout->ic_bypass) + ipu_free_irq(vout->work_irq, vout); + + if (vout->ic_bypass) + cancel_work_sync(&vout->timer_work); + + spin_lock_irqsave(&g_lock, lockflag); + + del_timer(&vout->output_timer); + + if (vout->state == STATE_STREAM_ON) { + vout->state = STATE_STREAM_STOPPING; + } + + spin_unlock_irqrestore(&g_lock, lockflag); + + if (vout->display_ch == MEM_FG_SYNC) { + struct mxcfb_pos fb_pos; + mm_segment_t old_fs; + + fb_pos.x = 0; + fb_pos.y = 0; + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS, + (unsigned long)&fb_pos); + set_fs(old_fs); + } + } + + if (vout->ic_bypass) { + fbi->var.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbi->var); + + if (vout->display_ch == MEM_FG_SYNC) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + release_console_sem(); + } + + vout->display_bufs[0] = 0; + vout->display_bufs[1] = 0; + } else if (vout->post_proc_ch == MEM_PP_MEM || + vout->post_proc_ch == MEM_PRP_VF_MEM) { + /* SDC with Rotation */ + if (!ipu_can_rotate_in_place(vout->rotate)) { + ipu_unlink_channels(MEM_PP_MEM, MEM_ROT_PP_MEM); + ipu_disable_channel(MEM_ROT_PP_MEM, true); + + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + } + ipu_disable_channel(MEM_PP_MEM, true); + + fbi->var.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbi->var); + + if (vout->display_ch == MEM_FG_SYNC) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + release_console_sem(); + } + + vout->display_bufs[0] = 0; + vout->display_bufs[1] = 0; + + ipu_uninit_channel(MEM_PP_MEM); + if (!ipu_can_rotate_in_place(vout->rotate)) + ipu_uninit_channel(MEM_ROT_PP_MEM); + } else if (INTERLACED_CONTENT(vout) && + (vout->post_proc_ch == MEM_VDI_PRP_VF_MEM)) { + if (!ipu_can_rotate_in_place(vout->rotate)) { + ipu_unlink_channels(MEM_VDI_PRP_VF_MEM, + MEM_ROT_VF_MEM); + ipu_disable_channel(MEM_ROT_VF_MEM, true); + + if (vout->rot_pp_bufs[0]) { + mxc_free_buffers(vout->rot_pp_bufs, + vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + } + } + + ipu_disable_channel(MEM_VDI_PRP_VF_MEM, true); + if (LOAD_3FIELDS(vout)) { + ipu_disable_channel(MEM_VDI_PRP_VF_MEM_P, true); + ipu_disable_channel(MEM_VDI_PRP_VF_MEM_N, true); + } + + fbi->var.activate |= FB_ACTIVATE_FORCE; + fb_set_var(fbi, &fbi->var); + + if (vout->display_ch == MEM_FG_SYNC) { + acquire_console_sem(); + fb_blank(fbi, FB_BLANK_POWERDOWN); + release_console_sem(); + } + + vout->display_bufs[0] = 0; + vout->display_bufs[1] = 0; + + ipu_uninit_channel(MEM_VDI_PRP_VF_MEM); + if (LOAD_3FIELDS(vout)) { + ipu_uninit_channel(MEM_VDI_PRP_VF_MEM_P); + ipu_uninit_channel(MEM_VDI_PRP_VF_MEM_N); + } + if (!ipu_can_rotate_in_place(vout->rotate)) + ipu_uninit_channel(MEM_ROT_VF_MEM); + } + + vout->ready_q.head = vout->ready_q.tail = 0; + vout->done_q.head = vout->done_q.tail = 0; + for (i = 0; i < vout->buffer_cnt; i++) { + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + + vout->post_proc_ch = CHAN_NONE; + vout->state = STATE_STREAM_OFF; + + return retval; +} + +/* + * Valid whether the palette is supported + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 1 if supported, 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return ((palette == V4L2_PIX_FMT_RGB565) || + (palette == V4L2_PIX_FMT_BGR24) || + (palette == V4L2_PIX_FMT_RGB24) || + (palette == V4L2_PIX_FMT_BGR32) || + (palette == V4L2_PIX_FMT_RGB32) || + (palette == V4L2_PIX_FMT_NV12) || + (palette == V4L2_PIX_FMT_UYVY) || + (palette == V4L2_PIX_FMT_YUYV) || + (palette == V4L2_PIX_FMT_YUV422P) || + (palette == V4L2_PIX_FMT_YUV420)); +} + +/* + * V4L2 - Handles VIDIOC_G_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_g_fmt(vout_data * vout, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + return -EINVAL; + } + *f = vout->v2f; + return 0; +} + +/* + * V4L2 - Handles VIDIOC_S_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_s_fmt(vout_data * vout, struct v4l2_format *f) +{ + int retval = 0; + u32 size = 0; + u32 bytesperline; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + goto err0; + } + if (!valid_mode(f->fmt.pix.pixelformat)) { + dev_err(&vout->video_dev->dev, "pixel format not supported\n"); + retval = -EINVAL; + goto err0; + } + + bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) / + 8; + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + vout->bytesperline = bytesperline; + + /* Based on http://v4l2spec.bytesex.org/spec/x6386.htm#V4L2-FIELD */ + vout->field_fmt = f->fmt.pix.field; + switch (vout->field_fmt) { + /* Images are in progressive format, not interlaced */ + case V4L2_FIELD_NONE: + break; + /* The two fields of a frame are passed in separate buffers, + in temporal order, i. e. the older one first. */ + case V4L2_FIELD_ALTERNATE: + dev_err(&vout->video_dev->dev, + "V4L2_FIELD_ALTERNATE field format not supported yet!\n"); + break; + case V4L2_FIELD_INTERLACED_TB: + if (cpu_is_mx51() || cpu_is_mx53()) + break; + dev_err(&vout->video_dev->dev, + "De-interlacing not supported in this device!\n"); + vout->field_fmt = V4L2_FIELD_NONE; + case V4L2_FIELD_INTERLACED_BT: + dev_err(&vout->video_dev->dev, + "V4L2_FIELD_INTERLACED_BT field format not supported yet!\n"); + default: + vout->field_fmt = V4L2_FIELD_NONE; + break; + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV422P: + /* byteperline for YUV planar formats is for + Y plane only */ + size = bytesperline * f->fmt.pix.height * 2; + break; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + size = (bytesperline * f->fmt.pix.height * 3) / 2; + break; + default: + size = bytesperline * f->fmt.pix.height; + break; + } + + /* Return the actual size of the image to the app */ + if (f->fmt.pix.sizeimage < size) { + f->fmt.pix.sizeimage = size; + } else { + size = f->fmt.pix.sizeimage; + } + + vout->v2f.fmt.pix = f->fmt.pix; + if (vout->v2f.fmt.pix.priv != 0) { + if (copy_from_user(&vout->offset, + (void *)vout->v2f.fmt.pix.priv, + sizeof(vout->offset))) { + retval = -EFAULT; + goto err0; + } + } else { + vout->offset.u_offset = 0; + vout->offset.v_offset = 0; + } + + retval = 0; + err0: + return retval; +} + +/* + * V4L2 - Handles VIDIOC_G_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0; + case V4L2_CID_VFLIP: + return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0; + case (V4L2_CID_PRIVATE_BASE + 1): + return vout->rotate; + default: + return -EINVAL; + } +} + +/* + * V4L2 - Handles VIDIOC_S_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + vout->rotate |= c->value ? IPU_ROTATE_HORIZ_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_VFLIP: + vout->rotate |= c->value ? IPU_ROTATE_VERT_FLIP : + IPU_ROTATE_NONE; + break; + case V4L2_CID_MXC_ROT: + vout->rotate = c->value; + break; + case V4L2_CID_MXC_MOTION: + vout->motion_sel = c->value; + break; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 interface - open function + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l2out_open(struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + int err; + + if (!vout) { + return -ENODEV; + } + + down(&vout->busy_lock); + + err = -EINTR; + if (signal_pending(current)) + goto oops; + + + if (vout->open_count++ == 0) { + init_waitqueue_head(&vout->v4l_bufq); + + init_timer(&vout->output_timer); + vout->output_timer.function = mxc_v4l2out_timer_handler; + vout->output_timer.data = (unsigned long)vout; + + vout->state = STATE_STREAM_OFF; + vout->rotate = IPU_ROTATE_NONE; + + vout->v4l_wq = create_singlethread_workqueue("v4l2q"); + if (!vout->v4l_wq) { + dev_dbg(&dev->dev, + "Could not create work queue\n"); + err = -ENOMEM; + goto oops; + } + + INIT_WORK(&vout->timer_work, timer_work_func); + } + + file->private_data = dev; + + up(&vout->busy_lock); + + return 0; + + oops: + up(&vout->busy_lock); + return err; +} + +/*! + * V4L2 interface - close function + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l2out_close(struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + if (--vout->open_count == 0) { + if (vout->state != STATE_STREAM_OFF) + mxc_v4l2out_streamoff(vout); + + file->private_data = NULL; + + mxc_free_buffers(vout->queue_buf_paddr, vout->queue_buf_vaddr, + vout->buffer_cnt, vout->queue_buf_size); + vout->buffer_cnt = 0; + mxc_free_buffers(vout->rot_pp_bufs, vout->rot_pp_bufs_vaddr, 2, + vout->display_buf_size); + + /* capture off */ + wake_up_interruptible(&vout->v4l_bufq); + + flush_workqueue(vout->v4l_wq); + destroy_workqueue(vout->v4l_wq); + } + + return 0; +} + +/*! + * V4L2 interface - ioctl function + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static long +mxc_v4l2out_do_ioctl(struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *vdev = file->private_data; + vout_data *vout = video_get_drvdata(vdev); + int retval = 0; + int i = 0; + + if (!vout) + return -EBADF; + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2_output"); + cap->version = 0; + cap->capabilities = + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *gf = arg; + retval = mxc_v4l2out_g_fmt(vout, gf); + break; + } + case VIDIOC_S_FMT: + { + struct v4l2_format *sf = arg; + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + retval = mxc_v4l2out_s_fmt(vout, sf); + break; + } + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (req->memory != V4L2_MEMORY_MMAP)) { + dev_dbg(&vdev->dev, + "VIDIOC_REQBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + + if (req->count == 0) + mxc_v4l2out_streamoff(vout); + + if (vout->state == STATE_STREAM_OFF) { + if (vout->queue_buf_paddr[0] != 0) { + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + dev_dbg(&vdev->dev, + "VIDIOC_REQBUFS: freed buffers\n"); + } + vout->buffer_cnt = 0; + } else { + dev_dbg(&vdev->dev, + "VIDIOC_REQBUFS: Buffer is in use\n"); + retval = -EBUSY; + break; + } + + if (req->count == 0) + break; + + if (req->count < MIN_FRAME_NUM) { + req->count = MIN_FRAME_NUM; + } else if (req->count > MAX_FRAME_NUM) { + req->count = MAX_FRAME_NUM; + } + vout->buffer_cnt = req->count; + vout->queue_buf_size = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + + retval = mxc_allocate_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + if (retval < 0) + break; + + /* Init buffer queues */ + vout->done_q.head = 0; + vout->done_q.tail = 0; + vout->ready_q.head = 0; + vout->ready_q.tail = 0; + + for (i = 0; i < vout->buffer_cnt; i++) { + memset(&(vout->v4l2_bufs[i]), 0, + sizeof(vout->v4l2_bufs[i])); + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP; + vout->v4l2_bufs[i].index = i; + vout->v4l2_bufs[i].type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + vout->v4l2_bufs[i].length = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + vout->v4l2_bufs[i].m.offset = + (unsigned long)vout->queue_buf_paddr[i]; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + break; + } + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + u32 type = buf->type; + int index = buf->index; + + if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + dev_dbg(&vdev->dev, + "VIDIOC_QUERYBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + down(&vout->param_lock); + memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf)); + up(&vout->param_lock); + break; + } + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int index = buf->index; + unsigned long lock_flags; + int param[5][3]; + + if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + retval = -EINVAL; + break; + } + + dev_dbg(&vdev->dev, "VIDIOC_QBUF: %d field = %d\n", buf->index, buf->field); + + /* mmapped buffers are L1 WB cached, + * so we need to clean them */ + if (buf->memory & V4L2_MEMORY_MMAP) { + flush_cache_all(); + } + + spin_lock_irqsave(&g_lock, lock_flags); + + memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf)); + vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED; + + g_buf_q_cnt++; + if (vout->v4l2_bufs[index].reserved) + if (!copy_from_user(¶m[0][0], + (void *)vout-> + v4l2_bufs[index] + .reserved, sizeof(param))) + ipu_set_csc_coefficients(vout-> + display_ch, + param); + queue_buf(&vout->ready_q, index); + if (vout->state == STATE_STREAM_PAUSED) { + index = peek_next_buf(&vout->ready_q); + setup_next_buf_timer(vout, index); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int idx; + + if ((queue_size(&vout->done_q) == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + if (!wait_event_interruptible_timeout(vout->v4l_bufq, + queue_size(&vout-> + done_q) + != 0, 10 * HZ)) { + dev_dbg(&vdev->dev, "VIDIOC_DQBUF: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + dev_dbg(&vdev->dev, + "VIDIOC_DQBUF: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + idx = dequeue_buf(&vout->done_q); + if (idx == -1) { /* No frame free */ + dev_dbg(&vdev->dev, + "VIDIOC_DQBUF: no free buffers, returning\n"); + retval = -EAGAIN; + break; + } + if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) == + 0) + dev_dbg(&vdev->dev, + "VIDIOC_DQBUF: buffer in done q, but not " + "flagged as done\n"); + + vout->v4l2_bufs[idx].flags = 0; + memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf)); + dev_dbg(&vdev->dev, "VIDIOC_DQBUF: %d\n", buf->index); + break; + } + case VIDIOC_STREAMON: + { + retval = mxc_v4l2out_streamon(vout); + break; + } + case VIDIOC_STREAMOFF: + { + retval = mxc_v4l2out_streamoff(vout); + break; + } + case VIDIOC_G_CTRL: + { + retval = mxc_get_v42lout_control(vout, arg); + break; + } + case VIDIOC_S_CTRL: + { + retval = mxc_set_v42lout_control(vout, arg); + break; + } + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + + cap->bounds = vout->crop_bounds[vout->cur_disp_output]; + cap->defrect = vout->crop_bounds[vout->cur_disp_output]; + retval = 0; + break; + } + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + crop->c = vout->crop_current; + break; + } + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = + &(vout->crop_bounds[vout->cur_disp_output]); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + if (crop->c.height < 0) { + retval = -EINVAL; + break; + } + if (crop->c.width < 0) { + retval = -EINVAL; + break; + } + + /* only full screen supported for SDC BG and SDC DC */ + if (vout->cur_disp_output == 4) { + crop->c = vout->crop_current; + break; + } + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top >= b->top + b->height) + crop->c.top = b->top + b->height - 1; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = + b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.left = b->left; + if (crop->c.left >= b->left + b->width) + crop->c.left = b->left + b->width - 1; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + /* stride line limitation */ + crop->c.height -= crop->c.height % 8; + crop->c.width -= crop->c.width % 8; + + vout->crop_current = crop->c; + break; + } + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if ((output->index >= 5) || + (vout->output_enabled[output->index] == false)) { + retval = -EINVAL; + break; + } + + if (output->index >= 3) + *output = mxc_outputs[MXC_V4L2_OUT_2_SDC]; + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = vout->cur_disp_output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + int fbnum; + struct v4l2_rect *b; + + if ((*p_output_num >= MXC_V4L2_OUT_NUM_OUTPUTS) || + (vout->output_enabled[*p_output_num] == false)) { + retval = -EINVAL; + break; + } + + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + + vout->cur_disp_output = *p_output_num; + + /* Update bounds in case they have changed */ + b = &vout->crop_bounds[vout->cur_disp_output]; + + fbnum = vout->output_fb_num[vout->cur_disp_output]; + + /* + * For FG overlay, it uses BG window parameter as + * limitation reference; and BG must be enabled to + * support FG. + */ + if (vout->cur_disp_output == 3) { + unsigned int i, ipu_ch = CHAN_NONE; + struct fb_info *fbi; + mm_segment_t old_fs; + + for (i = 0; i < num_registered_fb; i++) { + fbi = registered_fb[i]; + if (fbi->fbops->fb_ioctl) { + old_fs = get_fs(); + set_fs(KERNEL_DS); + fbi->fbops->fb_ioctl(fbi, + MXCFB_GET_FB_IPU_CHAN, + (unsigned long)&ipu_ch); + set_fs(old_fs); + } + if (ipu_ch == CHAN_NONE) { + dev_err(&vdev->dev, + "Can't get disp ipu channel\n"); + retval = -EINVAL; + break; + } + + if (ipu_ch == MEM_BG_SYNC) { + fbnum = i; + break; + } + } + } + + b->width = registered_fb[fbnum]->var.xres; + b->height = registered_fb[fbnum]->var.yres; + + vout->crop_current = *b; + break; + } + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_PARM: + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&vout->busy_lock); + return retval; +} + +/* + * V4L2 interface - ioctl function + * + * @return None + */ +static long +mxc_v4l2out_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(file, cmd, arg, mxc_v4l2out_do_ioctl); +} + +/*! + * V4L2 interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error + */ +static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *vdev = video_devdata(file); + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + int i; + vout_data *vout = video_get_drvdata(vdev); + + dev_dbg(&vdev->dev, "pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + for (i = 0; i < vout->buffer_cnt; i++) { + if ((vout->v4l2_bufs[i].m.offset == + (vma->vm_pgoff << PAGE_SHIFT)) && + (vout->v4l2_bufs[i].length >= size)) { + vout->v4l2_bufs[i].flags |= V4L2_BUF_FLAG_MAPPED; + break; + } + } + if (i == vout->buffer_cnt) { + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + /* make buffers inner write-back, outer write-thru cacheable */ + /* vma->vm_page_prot = pgprot_outer_wrthru(vma->vm_page_prot);*/ + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + dev_dbg(&vdev->dev, "mmap remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mxc_mmap_exit: + up(&vout->busy_lock); + return res; +} + +/*! + * V4L2 interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table_struct * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_v4l2out_poll(struct file *file, struct poll_table_struct * wait) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + queue = &vout->v4l_bufq; + poll_wait(file, queue, wait); + + up(&vout->busy_lock); + return res; +} + +static struct +v4l2_file_operations mxc_v4l2out_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l2out_open, + .release = mxc_v4l2out_close, + .ioctl = mxc_v4l2out_ioctl, + .mmap = mxc_v4l2out_mmap, + .poll = mxc_v4l2out_poll, +}; + +static struct video_device mxc_v4l2out_template = { + .name = "MXC Video Output", + .vfl_type = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING, + .fops = &mxc_v4l2out_fops, + .release = video_device_release, +}; + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxc_v4l2out_probe(struct platform_device *pdev) +{ + int i; + vout_data *vout; + + /* + * Allocate sufficient memory for the fb structure + */ + vout = kmalloc(sizeof(vout_data), GFP_KERNEL); + + if (!vout) + return 0; + + memset(vout, 0, sizeof(vout_data)); + + vout->video_dev = video_device_alloc(); + if (vout->video_dev == NULL) + return -1; + vout->video_dev->minor = -1; + + *(vout->video_dev) = mxc_v4l2out_template; + + /* register v4l device */ + if (video_register_device(vout->video_dev, + VFL_TYPE_GRABBER, video_nr) == -1) { + dev_dbg(&pdev->dev, "video_register_device failed\n"); + return 0; + } + dev_info(&pdev->dev, "Registered device video%d\n", + vout->video_dev->minor & 0x1f); + /*vout->video_dev->dev = &pdev->dev;*/ + + video_set_drvdata(vout->video_dev, vout); + + init_MUTEX(&vout->param_lock); + init_MUTEX(&vout->busy_lock); + + /* setup outputs and cropping */ + vout->cur_disp_output = -1; + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strncmp(idstr, "DISP", 4) == 0) { + int disp_num = idstr[4] - '0'; + if (disp_num == 3) { + if (strcmp(idstr, "DISP3 BG - DI1") == 0) + disp_num = 5; + else if (strncmp(idstr, "DISP3 BG", 8) == 0) + disp_num = 4; + } + vout->crop_bounds[disp_num].left = 0; + vout->crop_bounds[disp_num].top = 0; + vout->crop_bounds[disp_num].width = + registered_fb[i]->var.xres; + vout->crop_bounds[disp_num].height = + registered_fb[i]->var.yres; + vout->output_enabled[disp_num] = true; + vout->output_fb_num[disp_num] = i; + if (vout->cur_disp_output == -1) { + vout->cur_disp_output = disp_num; + } + } + + } + vout->crop_current = vout->crop_bounds[vout->cur_disp_output]; + + platform_set_drvdata(pdev, vout); + + return 0; +} + +static int mxc_v4l2out_remove(struct platform_device *pdev) +{ + vout_data *vout = platform_get_drvdata(pdev); + + if (vout->video_dev) { + video_unregister_device(vout->video_dev); + vout->video_dev = NULL; + } + + platform_set_drvdata(pdev, NULL); + + kfree(vout); + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2out_driver = { + .driver = { + .name = "mxc_v4l2_output", + }, + .probe = mxc_v4l2out_probe, + .remove = mxc_v4l2out_remove, +}; + +/*! + * mxc v4l2 init function + * + */ +static int mxc_v4l2out_init(void) +{ + return platform_driver_register(&mxc_v4l2out_driver); +} + +/*! + * mxc v4l2 cleanup function + * + */ +static void mxc_v4l2out_clean(void) +{ + platform_driver_unregister(&mxc_v4l2out_driver); +} + +module_init(mxc_v4l2out_init); +module_exit(mxc_v4l2out_clean); + +module_param(video_nr, int, 0444); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2-driver for MXC video output"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/video/mxc/output/mxc_v4l2_output.h b/drivers/media/video/mxc/output/mxc_v4l2_output.h new file mode 100644 index 000000000000..096dc3b17a06 --- /dev/null +++ b/drivers/media/video/mxc/output/mxc_v4l2_output.h @@ -0,0 +1,154 @@ +/* + * Copyright 2005-2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup MXC_V4L2_OUTPUT MXC V4L2 Video Output Driver + */ +/*! + * @file mxc_v4l2_output.h + * + * @brief MXC V4L2 Video Output Driver Header file + * + * Video4Linux2 Output Device using MXC IPU Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#ifndef __MXC_V4L2_OUTPUT_H__ +#define __MXC_V4L2_OUTPUT_H__ + +#include <media/v4l2-dev.h> + +#ifdef __KERNEL__ + +#include <linux/ipu.h> +#include <linux/mxc_v4l2.h> +#include <linux/videodev2.h> + +#define MIN_FRAME_NUM 2 +#define MAX_FRAME_NUM 30 + +#define MXC_V4L2_OUT_NUM_OUTPUTS 6 +#define MXC_V4L2_OUT_2_SDC 0 + + +typedef struct { + int list[MAX_FRAME_NUM + 1]; + int head; + int tail; +} v4l_queue; + +/*! + * States for the video stream + */ +typedef enum { + STATE_STREAM_OFF, + STATE_STREAM_ON, + STATE_STREAM_PAUSED, + STATE_STREAM_STOPPING, +} v4lout_state; + +/*! + * common v4l2 driver structure. + */ +typedef struct _vout_data { + struct video_device *video_dev; + /*! + * semaphore guard against SMP multithreading + */ + struct semaphore busy_lock; + + /*! + * number of process that have device open + */ + int open_count; + + /*! + * params lock for this camera + */ + struct semaphore param_lock; + + struct timer_list output_timer; + struct workqueue_struct *v4l_wq; + struct work_struct timer_work; + unsigned long start_jiffies; + u32 frame_count; + + v4l_queue ready_q; + v4l_queue done_q; + + s8 next_rdy_ipu_buf; + s8 next_done_ipu_buf; + s8 ipu_buf[2]; + s8 ipu_buf_p[2]; + s8 ipu_buf_n[2]; + volatile v4lout_state state; + + int cur_disp_output; + int output_fb_num[MXC_V4L2_OUT_NUM_OUTPUTS]; + int output_enabled[MXC_V4L2_OUT_NUM_OUTPUTS]; + struct v4l2_framebuffer v4l2_fb; + int ic_bypass; + u32 work_irq; + ipu_channel_t display_ch; + ipu_channel_t post_proc_ch; + ipu_channel_t display_input_ch; + + /*! + * FRAME_NUM-buffering, so we need a array + */ + int buffer_cnt; + dma_addr_t queue_buf_paddr[MAX_FRAME_NUM]; + void *queue_buf_vaddr[MAX_FRAME_NUM]; + u32 queue_buf_size; + struct v4l2_buffer v4l2_bufs[MAX_FRAME_NUM]; + u32 display_buf_size; + dma_addr_t display_bufs[2]; + void *display_bufs_vaddr[2]; + dma_addr_t rot_pp_bufs[2]; + void *rot_pp_bufs_vaddr[2]; + + /*! + * Poll wait queue + */ + wait_queue_head_t v4l_bufq; + + /*! + * v4l2 format + */ + struct v4l2_format v2f; + struct v4l2_mxc_offset offset; + ipu_rotate_mode_t rotate; + + /* crop */ + struct v4l2_rect crop_bounds[MXC_V4L2_OUT_NUM_OUTPUTS]; + struct v4l2_rect crop_current; + u32 bytesperline; + enum v4l2_field field_fmt; + ipu_motion_sel motion_sel; + + /* PP split fot two stripes*/ + int pp_split; /* 0,1 */ + struct stripe_param pp_left_stripe; + struct stripe_param pp_right_stripe; /* struct for split parameters */ + struct stripe_param pp_up_stripe; + struct stripe_param pp_down_stripe; + /* IC ouput buffer number. Counting from 0 to 7 */ + int pp_split_buf_num; /* 0..7 */ + u16 bpp ; /* bit per pixel */ + u16 xres; /* width of physical frame (BGs) */ + u16 yres; /* heigth of physical frame (BGs)*/ + +} vout_data; + +#endif +#endif /* __MXC_V4L2_OUTPUT_H__ */ diff --git a/drivers/media/video/mxs_pxp.c b/drivers/media/video/mxs_pxp.c new file mode 100644 index 000000000000..017d22458a22 --- /dev/null +++ b/drivers/media/video/mxs_pxp.c @@ -0,0 +1,1413 @@ +/* + * Freescale MXS PxP driver + * + * Author: Matt Porter <mporter@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/vmalloc.h> +#include <linux/videodev2.h> +#include <linux/delay.h> + +#include <media/videobuf-dma-contig.h> +#include <media/v4l2-common.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> + +#include <mach/hardware.h> +#include <mach/regs-pxp.h> + +#include "mxs_pxp.h" + +#define PXP_BASE_ADDR IO_ADDRESS(PXP_PHYS_ADDR) + +#define PXP_DRIVER_NAME "mxs-pxp" +#define PXP_DRIVER_MAJOR 1 +#define PXP_DRIVER_MINOR 0 + +#define PXP_DEF_BUFS 2 +#define PXP_MIN_PIX 8 + +#define V4L2_OUTPUT_TYPE_INTERNAL 4 + +#define PXP_WAITCON ((__raw_readl(PXP_BASE_ADDR + HW_PXP_NEXT) & \ + BM_PXP_NEXT_ENABLED) != BM_PXP_NEXT_ENABLED) + +#define REG_OFFSET 0x10 +#define REGS1_NUMS 16 +#define REGS2_NUMS 5 +#define REGS3_NUMS 32 +static u32 regs1[REGS1_NUMS]; +static u32 regs2[REGS2_NUMS]; +static u32 regs3[REGS3_NUMS]; + +static struct pxp_data_format pxp_s0_formats[] = { + { + .name = "24-bit RGB", + .bpp = 4, + .fourcc = V4L2_PIX_FMT_RGB24, + .colorspace = V4L2_COLORSPACE_SRGB, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__RGB888, + }, + { + .name = "16-bit RGB 5:6:5", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_RGB565, + .colorspace = V4L2_COLORSPACE_SRGB, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__RGB565, + }, + { + .name = "16-bit RGB 5:5:5", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_RGB555, + .colorspace = V4L2_COLORSPACE_SRGB, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__RGB555, + }, + { + .name = "YUV 4:2:0 Planar", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_YUV420, + .colorspace = V4L2_COLORSPACE_JPEG, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__YUV420, + }, + { + .name = "YUV 4:2:2 Planar", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_YUV422P, + .colorspace = V4L2_COLORSPACE_JPEG, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__YUV422, + }, +}; + +struct v4l2_queryctrl pxp_controls[] = { + { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Horizontal Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, + { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Vertical Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, + { + .id = V4L2_CID_PRIVATE_BASE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Rotation", + .minimum = 0, + .maximum = 270, + .step = 90, + .default_value = 0, + .flags = 0, + }, + { + .id = V4L2_CID_PRIVATE_BASE + 1, + .name = "Background Color", + .minimum = 0, + .maximum = 0xFFFFFF, + .step = 1, + .default_value = 0, + .flags = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + { + .id = V4L2_CID_PRIVATE_BASE + 2, + .name = "Set S0 Chromakey", + .minimum = -1, + .maximum = 0xFFFFFF, + .step = 1, + .default_value = -1, + .flags = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, + { + .id = V4L2_CID_PRIVATE_BASE + 3, + .name = "YUV Colorspace", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, +}; + +static void pxp_set_ctrl(struct pxps *pxp) +{ + u32 ctrl; + + ctrl = BF_PXP_CTRL_S0_FORMAT(pxp->s0_fmt->ctrl_s0_fmt); + ctrl |= BF_PXP_CTRL_OUTBUF_FORMAT(BV_PXP_CTRL_OUTBUF_FORMAT__RGB888); + ctrl |= BM_PXP_CTRL_CROP; + + if (pxp->scaling) + ctrl |= BM_PXP_CTRL_SCALE; + if (pxp->vflip) + ctrl |= BM_PXP_CTRL_VFLIP; + if (pxp->hflip) + ctrl |= BM_PXP_CTRL_HFLIP; + if (pxp->rotate) + ctrl |= BF_PXP_CTRL_ROTATE(pxp->rotate / 90); + + ctrl |= BM_PXP_CTRL_IRQ_ENABLE; + if (pxp->active) + ctrl |= BM_PXP_CTRL_ENABLE; + + __raw_writel(ctrl, PXP_BASE_ADDR + HW_PXP_CTRL); + pxp->regs_virt->ctrl = ctrl; +} + +static void pxp_set_outbuf(struct pxps *pxp) +{ + pxp->regs_virt->outbuf = pxp->outb_phys; + /* Always equal to the FB size */ + if (pxp->rotate % 180) + pxp->regs_virt->outsize = + BF_PXP_OUTSIZE_WIDTH(pxp->fb.fmt.height) | + BF_PXP_OUTSIZE_HEIGHT(pxp->fb.fmt.width); + else + pxp->regs_virt->outsize = + BF_PXP_OUTSIZE_WIDTH(pxp->fb.fmt.width) | + BF_PXP_OUTSIZE_HEIGHT(pxp->fb.fmt.height); +} + +static void pxp_set_s0colorkey(struct pxps *pxp) +{ + /* Low and high are set equal. V4L does not allow a chromakey range */ + if (pxp->s0_chromakey == -1) { + /* disable color key */ + pxp->regs_virt->s0colorkeylow = 0xFFFFFF; + pxp->regs_virt->s0colorkeyhigh = 0; + } else { + pxp->regs_virt->s0colorkeylow = pxp->s0_chromakey; + pxp->regs_virt->s0colorkeyhigh = pxp->s0_chromakey; + } +} + +static void pxp_set_s1colorkey(struct pxps *pxp) +{ + /* Low and high are set equal. V4L does not allow a chromakey range */ + if (pxp->s1_chromakey_state != 0 && pxp->s1_chromakey != -1) { + pxp->regs_virt->olcolorkeylow = pxp->s1_chromakey; + pxp->regs_virt->olcolorkeyhigh = pxp->s1_chromakey; + } else { + /* disable color key */ + pxp->regs_virt->olcolorkeylow = 0xFFFFFF; + pxp->regs_virt->olcolorkeyhigh = 0; + } +} + +static void pxp_set_oln(struct pxps *pxp) +{ + pxp->regs_virt->ol0.ol = (u32) pxp->fb.base; + if (pxp->rotate % 180) + pxp->regs_virt->ol0.olsize = + BF_PXP_OLnSIZE_WIDTH(pxp->fb.fmt.height >> 3) | + BF_PXP_OLnSIZE_HEIGHT(pxp->fb.fmt.width >> 3); + else + pxp->regs_virt->ol0.olsize = + BF_PXP_OLnSIZE_WIDTH(pxp->fb.fmt.width >> 3) | + BF_PXP_OLnSIZE_HEIGHT(pxp->fb.fmt.height >> 3); +} + +static void pxp_set_olparam(struct pxps *pxp) +{ + u32 olparam; + struct v4l2_pix_format *fmt = &pxp->fb.fmt; + + olparam = BF_PXP_OLnPARAM_ALPHA(pxp->global_alpha); + if (fmt->pixelformat == V4L2_PIX_FMT_RGB24) + olparam |= + BF_PXP_OLnPARAM_FORMAT(BV_PXP_OLnPARAM_FORMAT__RGB888); + else + olparam |= + BF_PXP_OLnPARAM_FORMAT(BV_PXP_OLnPARAM_FORMAT__RGB565); + if (pxp->global_alpha_state) + olparam |= + BF_PXP_OLnPARAM_ALPHA_CNTL + (BV_PXP_OLnPARAM_ALPHA_CNTL__Override); + if (pxp->s1_chromakey_state) + olparam |= BM_PXP_OLnPARAM_ENABLE_COLORKEY; + if (pxp->overlay_state) + olparam |= BM_PXP_OLnPARAM_ENABLE; + + pxp->regs_virt->ol0.olparam = olparam; +} + +static void pxp_set_s0param(struct pxps *pxp) +{ + u32 s0param; + + s0param = BF_PXP_S0PARAM_XBASE(pxp->drect.left >> 3); + s0param |= BF_PXP_S0PARAM_YBASE(pxp->drect.top >> 3); + s0param |= BF_PXP_S0PARAM_WIDTH(pxp->s0_width >> 3); + s0param |= BF_PXP_S0PARAM_HEIGHT(pxp->s0_height >> 3); + pxp->regs_virt->s0param = s0param; +} + +static void pxp_set_s0crop(struct pxps *pxp) +{ + u32 s0crop; + + s0crop = BF_PXP_S0CROP_XBASE(pxp->srect.left >> 3); + s0crop |= BF_PXP_S0CROP_YBASE(pxp->srect.top >> 3); + s0crop |= BF_PXP_S0CROP_WIDTH(pxp->drect.width >> 3); + s0crop |= BF_PXP_S0CROP_HEIGHT(pxp->drect.height >> 3); + pxp->regs_virt->s0crop = s0crop; +} + +static int pxp_set_scaling(struct pxps *pxp) +{ + int ret = 0; + u32 xscale, yscale, s0scale; + + if ((pxp->s0_fmt->fourcc != V4L2_PIX_FMT_YUV420) && + (pxp->s0_fmt->fourcc != V4L2_PIX_FMT_YUV422P)) { + pxp->scaling = 0; + ret = -EINVAL; + goto out; + } + + if ((pxp->srect.width == pxp->drect.width) && + (pxp->srect.height == pxp->drect.height)) { + pxp->regs_virt->s0scale = 0x10001000; + pxp->scaling = 0; + goto out; + } + + pxp->scaling = 1; + xscale = pxp->srect.width * 0x1000 / pxp->drect.width; + yscale = pxp->srect.height * 0x1000 / pxp->drect.height; + if (xscale > PXP_DOWNSCALE_THRESHOLD) + xscale = PXP_DOWNSCALE_THRESHOLD; + if (yscale > PXP_DOWNSCALE_THRESHOLD) + yscale = PXP_DOWNSCALE_THRESHOLD; + s0scale = BF_PXP_S0SCALE_YSCALE(yscale) | BF_PXP_S0SCALE_XSCALE(xscale); + pxp->regs_virt->s0scale = s0scale; + +out: + pxp_set_ctrl(pxp); + + return ret; +} + +static int pxp_set_fbinfo(struct pxps *pxp) +{ + struct fb_var_screeninfo var; + struct fb_fix_screeninfo fix; + struct v4l2_framebuffer *fb = &pxp->fb; + int err; + + err = mxsfb_get_info(&var, &fix); + + fb->fmt.width = var.xres; + fb->fmt.height = var.yres; + if (var.bits_per_pixel == 16) + fb->fmt.pixelformat = V4L2_PIX_FMT_RGB565; + else + fb->fmt.pixelformat = V4L2_PIX_FMT_RGB24; + fb->base = (void *)fix.smem_start; + return err; +} + +static void pxp_set_s0bg(struct pxps *pxp) +{ + pxp->regs_virt->s0background = pxp->s0_bgcolor; +} + +static void pxp_set_csc(struct pxps *pxp) +{ + if (pxp->yuv) { + /* YUV colorspace */ + __raw_writel(0x04030000, PXP_BASE_ADDR + HW_PXP_CSCCOEFF0); + __raw_writel(0x01230208, PXP_BASE_ADDR + HW_PXP_CSCCOEFF1); + __raw_writel(0x076b079b, PXP_BASE_ADDR + HW_PXP_CSCCOEFF2); + } else { + /* YCrCb colorspace */ + __raw_writel(0x84ab01f0, PXP_BASE_ADDR + HW_PXP_CSCCOEFF0); + __raw_writel(0x01230204, PXP_BASE_ADDR + HW_PXP_CSCCOEFF1); + __raw_writel(0x0730079c, PXP_BASE_ADDR + HW_PXP_CSCCOEFF2); + } +} + +static int pxp_set_cstate(struct pxps *pxp, struct v4l2_control *vc) +{ + + if (vc->id == V4L2_CID_HFLIP) + pxp->hflip = vc->value; + else if (vc->id == V4L2_CID_VFLIP) + pxp->vflip = vc->value; + else if (vc->id == V4L2_CID_PRIVATE_BASE) { + if (vc->value % 90) + return -ERANGE; + pxp->rotate = vc->value; + } else if (vc->id == V4L2_CID_PRIVATE_BASE + 1) { + pxp->s0_bgcolor = vc->value; + pxp_set_s0bg(pxp); + } else if (vc->id == V4L2_CID_PRIVATE_BASE + 2) { + pxp->s0_chromakey = vc->value; + pxp_set_s0colorkey(pxp); + } else if (vc->id == V4L2_CID_PRIVATE_BASE + 3) { + pxp->yuv = vc->value; + pxp_set_csc(pxp); + } + + pxp_set_ctrl(pxp); + + return 0; +} + +static int pxp_get_cstate(struct pxps *pxp, struct v4l2_control *vc) +{ + if (vc->id == V4L2_CID_HFLIP) + vc->value = pxp->hflip; + else if (vc->id == V4L2_CID_VFLIP) + vc->value = pxp->vflip; + else if (vc->id == V4L2_CID_PRIVATE_BASE) + vc->value = pxp->rotate; + else if (vc->id == V4L2_CID_PRIVATE_BASE + 1) + vc->value = pxp->s0_bgcolor; + else if (vc->id == V4L2_CID_PRIVATE_BASE + 2) + vc->value = pxp->s0_chromakey; + else if (vc->id == V4L2_CID_PRIVATE_BASE + 3) + vc->value = pxp->yuv; + + return 0; +} + +static int pxp_enumoutput(struct file *file, void *fh, struct v4l2_output *o) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + if ((o->index < 0) || (o->index > 1)) + return -EINVAL; + + memset(o, 0, sizeof(struct v4l2_output)); + if (o->index == 0) { + strcpy(o->name, "PxP Display Output"); + pxp->output = 0; + } else { + strcpy(o->name, "PxP Virtual Output"); + pxp->output = 1; + } + o->type = V4L2_OUTPUT_TYPE_INTERNAL; + o->std = 0; + o->reserved[0] = pxp->outb_phys; + + return 0; +} + +static int pxp_g_output(struct file *file, void *fh, unsigned int *i) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + *i = pxp->output; + + return 0; +} + +static int pxp_s_output(struct file *file, void *fh, unsigned int i) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_pix_format *fmt = &pxp->fb.fmt; + int bpp; + + if ((i < 0) || (i > 1)) + return -EINVAL; + + if (pxp->outb) + goto out; + + /* Output buffer is same format as fbdev */ + if (fmt->pixelformat == V4L2_PIX_FMT_RGB24) + bpp = 4; + else + bpp = 2; + + pxp->outb = kmalloc(fmt->width * fmt->height * bpp, GFP_KERNEL); + pxp->outb_phys = virt_to_phys(pxp->outb); + dma_map_single(NULL, pxp->outb, + fmt->width * fmt->height * bpp, DMA_TO_DEVICE); + +out: + pxp_set_outbuf(pxp); + + return 0; +} + +static int pxp_enum_fmt_video_output(struct file *file, void *fh, + struct v4l2_fmtdesc *fmt) +{ + enum v4l2_buf_type type = fmt->type; + int index = fmt->index; + + if ((fmt->index < 0) || (fmt->index >= ARRAY_SIZE(pxp_s0_formats))) + return -EINVAL; + + memset(fmt, 0, sizeof(struct v4l2_fmtdesc)); + fmt->index = index; + fmt->type = type; + fmt->pixelformat = pxp_s0_formats[index].fourcc; + strcpy(fmt->description, pxp_s0_formats[index].name); + + return 0; +} + +static int pxp_g_fmt_video_output(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct v4l2_pix_format *pf = &f->fmt.pix; + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct pxp_data_format *fmt = pxp->s0_fmt; + + pf->width = pxp->s0_width; + pf->height = pxp->s0_height; + pf->pixelformat = fmt->fourcc; + pf->field = V4L2_FIELD_NONE; + pf->bytesperline = fmt->bpp * pf->width; + pf->sizeimage = pf->bytesperline * pf->height; + pf->colorspace = fmt->colorspace; + pf->priv = 0; + + return 0; +} + +static struct pxp_data_format *pxp_get_format(struct v4l2_format *f) +{ + struct pxp_data_format *fmt; + int i; + + for (i = 0; i < ARRAY_SIZE(pxp_s0_formats); i++) { + fmt = &pxp_s0_formats[i]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (i == ARRAY_SIZE(pxp_s0_formats)) + return NULL; + + return &pxp_s0_formats[i]; +} + +static int pxp_try_fmt_video_output(struct file *file, void *fh, + struct v4l2_format *f) +{ + int w = f->fmt.pix.width; + int h = f->fmt.pix.height; + struct pxp_data_format *fmt = pxp_get_format(f); + + if (!fmt) + return -EINVAL; + + w = min(w, 2040); + w = max(w, 8); + h = min(h, 2040); + h = max(h, 8); + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = w; + f->fmt.pix.height = h; + f->fmt.pix.pixelformat = fmt->fourcc; + + return 0; +} + +static int pxp_s_fmt_video_output(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_pix_format *pf = &f->fmt.pix; + int ret = pxp_try_fmt_video_output(file, fh, f); + + if (ret == 0) { + pxp->s0_fmt = pxp_get_format(f); + pxp->s0_width = pf->width; + pxp->s0_height = pf->height; + pxp_set_ctrl(pxp); + pxp_set_s0param(pxp); + } + + return ret; +} + +static int pxp_g_fmt_output_overlay(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_window *wf = &f->fmt.win; + + memset(wf, 0, sizeof(struct v4l2_window)); + wf->chromakey = pxp->s1_chromakey; + wf->global_alpha = pxp->global_alpha; + wf->field = V4L2_FIELD_NONE; + wf->clips = NULL; + wf->clipcount = 0; + wf->bitmap = NULL; + wf->w.left = pxp->srect.left; + wf->w.top = pxp->srect.top; + wf->w.width = pxp->srect.width; + wf->w.height = pxp->srect.height; + + return 0; +} + +static int pxp_try_fmt_output_overlay(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_window *wf = &f->fmt.win; + struct v4l2_rect srect; + u32 s1_chromakey = wf->chromakey; + u8 global_alpha = wf->global_alpha; + + memcpy(&srect, &(wf->w), sizeof(struct v4l2_rect)); + + pxp_g_fmt_output_overlay(file, fh, f); + + wf->chromakey = s1_chromakey; + wf->global_alpha = global_alpha; + + /* Constrain parameters to the input buffer */ + wf->w.left = srect.left; + wf->w.top = srect.top; + wf->w.width = min(srect.width, ((__s32) pxp->s0_width - wf->w.left)); + wf->w.height = min(srect.height, ((__s32) pxp->s0_height - wf->w.top)); + + return 0; +} + +static int pxp_s_fmt_output_overlay(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_window *wf = &f->fmt.win; + int ret = pxp_try_fmt_output_overlay(file, fh, f); + + if (ret == 0) { + pxp->srect.left = wf->w.left; + pxp->srect.top = wf->w.top; + pxp->srect.width = wf->w.width; + pxp->srect.height = wf->w.height; + pxp->global_alpha = wf->global_alpha; + pxp->s1_chromakey = wf->chromakey; + pxp_set_s0param(pxp); + pxp_set_s0crop(pxp); + pxp_set_scaling(pxp); + pxp_set_olparam(pxp); + pxp_set_s1colorkey(pxp); + } + + return ret; +} + +static int pxp_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *r) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_reqbufs(&pxp->s0_vbq, r); +} + +static int pxp_querybuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_querybuf(&pxp->s0_vbq, b); +} + +static int pxp_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_qbuf(&pxp->s0_vbq, b); +} + +static int pxp_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_dqbuf(&pxp->s0_vbq, b, file->f_flags & O_NONBLOCK); +} + +static int pxp_streamon(struct file *file, void *priv, enum v4l2_buf_type t) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret = 0; + + if ((t != V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -EINVAL; + + pxp_set_outbuf(pxp); + ret = videobuf_streamon(&pxp->s0_vbq); + msleep(20); + + if (!ret && (pxp->output == 0)) + mxsfb_cfg_pxp(1, pxp->outb_phys); + + return ret; +} + +static int pxp_streamoff(struct file *file, void *priv, enum v4l2_buf_type t) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret = 0; + + if ((t != V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -EINVAL; + + cancel_work_sync(&pxp->work); + ret = videobuf_streamoff(&pxp->s0_vbq); + msleep(20); + + if (!ret) + mxsfb_cfg_pxp(0, 0); + + return ret; +} + +static int pxp_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned *size) +{ + struct pxps *pxp = q->priv_data; + + *size = pxp->s0_width * pxp->s0_height * pxp->s0_fmt->bpp; + + if (0 == *count) + *count = PXP_DEF_BUFS; + + return 0; +} + +static void pxp_buf_free(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + if (in_interrupt()) + BUG(); + + videobuf_dma_contig_free(q, vb); + + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static int pxp_buf_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct pxps *pxp = q->priv_data; + int ret = 0; + + vb->width = pxp->s0_width; + vb->height = pxp->s0_height; + vb->size = vb->width * vb->height * pxp->s0_fmt->bpp; + vb->field = V4L2_FIELD_NONE; + vb->state = VIDEOBUF_NEEDS_INIT; + + ret = videobuf_iolock(q, vb, NULL); + if (ret) + goto fail; + vb->state = VIDEOBUF_PREPARED; + + return 0; + +fail: + pxp_buf_free(q, vb); + return ret; +} + +static void pxp_buf_next(struct pxps *pxp) +{ + dma_addr_t Y, U, V; + + if (pxp->active) { + pxp->active->state = VIDEOBUF_ACTIVE; + Y = videobuf_to_dma_contig(pxp->active); + pxp->regs_virt->s0buf = Y; + if ((pxp->s0_fmt->fourcc == V4L2_PIX_FMT_YUV420) || + (pxp->s0_fmt->fourcc == V4L2_PIX_FMT_YUV422P)) { + int s = 1; /* default to YUV 4:2:2 */ + if (pxp->s0_fmt->fourcc == V4L2_PIX_FMT_YUV420) + s = 2; + U = Y + (pxp->s0_width * pxp->s0_height); + V = U + ((pxp->s0_width * pxp->s0_height) >> s); + pxp->regs_virt->s0ubuf = U; + pxp->regs_virt->s0vbuf = V; + } + pxp->regs_virt->ctrl = + __raw_readl(PXP_BASE_ADDR + + HW_PXP_CTRL) | BM_PXP_CTRL_ENABLE; + } + + __raw_writel(pxp->regs_phys, PXP_BASE_ADDR + HW_PXP_NEXT); +} + +static void pxp_next_handle(struct work_struct *w) +{ + struct pxps *pxp = container_of(w, struct pxps, work); + struct pxp_buffer *buf, *next; + unsigned long flags; + + if (pxp->next_queue_ended == 1) + return; + + spin_lock_irqsave(&pxp->lock, flags); + + while (!list_empty(&pxp->nextq)) { + spin_unlock_irqrestore(&pxp->lock, flags); + + if (!wait_event_interruptible_timeout(pxp->done, PXP_WAITCON, + 5 * HZ) + || signal_pending(current)) { + spin_lock_irqsave(&pxp->lock, flags); + list_for_each_entry_safe(buf, next, &pxp->nextq, queue) + list_del(&buf->queue); + spin_unlock_irqrestore(&pxp->lock, flags); + pxp->next_queue_ended = 1; + return; + } + + spin_lock_irqsave(&pxp->lock, flags); + buf = list_entry(pxp->nextq.next, struct pxp_buffer, queue); + list_del_init(&buf->queue); + pxp->active = &buf->vb; + pxp->active->state = VIDEOBUF_QUEUED; + pxp_buf_next(pxp); + } + + spin_unlock_irqrestore(&pxp->lock, flags); +} + +static void pxp_buf_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct pxps *pxp = q->priv_data; + struct pxp_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&pxp->lock, flags); + + if (list_empty(&pxp->outq)) { + list_add_tail(&vb->queue, &pxp->outq); + vb->state = VIDEOBUF_QUEUED; + + pxp->active = vb; + pxp_buf_next(pxp); + } else { + list_add_tail(&vb->queue, &pxp->outq); + + buf = container_of(vb, struct pxp_buffer, vb); + list_add_tail(&buf->queue, &pxp->nextq); + queue_work(pxp->workqueue, &pxp->work); + } + + spin_unlock_irqrestore(&pxp->lock, flags); +} + +static void pxp_buf_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + pxp_buf_free(q, vb); +} + +static struct videobuf_queue_ops pxp_vbq_ops = { + .buf_setup = pxp_buf_setup, + .buf_prepare = pxp_buf_prepare, + .buf_queue = pxp_buf_queue, + .buf_release = pxp_buf_release, +}; + +static int pxp_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + memset(cap, 0, sizeof(*cap)); + strcpy(cap->driver, "pxp"); + strcpy(cap->card, "pxp"); + strlcpy(cap->bus_info, dev_name(&pxp->pdev->dev), + sizeof(cap->bus_info)); + + cap->version = (PXP_DRIVER_MAJOR << 8) + PXP_DRIVER_MINOR; + + cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_OUTPUT_OVERLAY | V4L2_CAP_STREAMING; + + return 0; +} + +static int pxp_g_fbuf(struct file *file, void *priv, + struct v4l2_framebuffer *fb) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + memset(fb, 0, sizeof(*fb)); + + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY | + V4L2_FBUF_CAP_CHROMAKEY | + V4L2_FBUF_CAP_LOCAL_ALPHA | V4L2_FBUF_CAP_GLOBAL_ALPHA; + + if (pxp->global_alpha_state) + fb->flags |= V4L2_FBUF_FLAG_GLOBAL_ALPHA; + if (pxp->local_alpha_state) + fb->flags |= V4L2_FBUF_FLAG_LOCAL_ALPHA; + if (pxp->s1_chromakey_state) + fb->flags |= V4L2_FBUF_FLAG_CHROMAKEY; + + return 0; +} + +static int pxp_s_fbuf(struct file *file, void *priv, + struct v4l2_framebuffer *fb) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + pxp->overlay_state = (fb->flags & V4L2_FBUF_FLAG_OVERLAY) != 0; + pxp->global_alpha_state = + (fb->flags & V4L2_FBUF_FLAG_GLOBAL_ALPHA) != 0; + pxp->local_alpha_state = (fb->flags & V4L2_FBUF_FLAG_LOCAL_ALPHA) != 0; + /* Global alpha overrides local alpha if both are requested */ + if (pxp->global_alpha_state && pxp->local_alpha_state) + pxp->local_alpha_state = 0; + pxp->s1_chromakey_state = (fb->flags & V4L2_FBUF_FLAG_CHROMAKEY) != 0; + + pxp_set_olparam(pxp); + pxp_set_s0crop(pxp); + pxp_set_scaling(pxp); + + return 0; +} + +static int pxp_g_crop(struct file *file, void *fh, struct v4l2_crop *c) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY) + return -EINVAL; + + c->c.left = pxp->drect.left; + c->c.top = pxp->drect.top; + c->c.width = pxp->drect.width; + c->c.height = pxp->drect.height; + + return 0; +} + +static int pxp_s_crop(struct file *file, void *fh, struct v4l2_crop *c) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int l = c->c.left; + int t = c->c.top; + int w = c->c.width; + int h = c->c.height; + int fbw = pxp->fb.fmt.width; + int fbh = pxp->fb.fmt.height; + + if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY) + return -EINVAL; + + /* Constrain parameters to FB limits */ + w = min(w, fbw); + w = max(w, PXP_MIN_PIX); + h = min(h, fbh); + h = max(h, PXP_MIN_PIX); + if ((l + w) > fbw) + l = 0; + if ((t + h) > fbh) + t = 0; + + /* Round up values to PxP pixel block */ + l = roundup(l, PXP_MIN_PIX); + t = roundup(t, PXP_MIN_PIX); + w = roundup(w, PXP_MIN_PIX); + h = roundup(h, PXP_MIN_PIX); + + pxp->drect.left = l; + pxp->drect.top = t; + pxp->drect.width = w; + pxp->drect.height = h; + + pxp_set_s0param(pxp); + pxp_set_s0crop(pxp); + pxp_set_scaling(pxp); + + return 0; +} + +static int pxp_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) + if (qc->id && qc->id == pxp_controls[i].id) { + memcpy(qc, &(pxp_controls[i]), sizeof(*qc)); + return 0; + } + + return -EINVAL; +} + +static int pxp_g_ctrl(struct file *file, void *priv, struct v4l2_control *vc) +{ + int i; + + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) + if (vc->id == pxp_controls[i].id) + return pxp_get_cstate(pxp, vc); + + return -EINVAL; +} + +static int pxp_s_ctrl(struct file *file, void *priv, struct v4l2_control *vc) +{ + int i; + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) + if (vc->id == pxp_controls[i].id) { + if (vc->value < pxp_controls[i].minimum || + vc->value > pxp_controls[i].maximum) + return -ERANGE; + return pxp_set_cstate(pxp, vc); + } + + return -EINVAL; +} + +void pxp_release(struct video_device *vfd) +{ + struct pxps *pxp = video_get_drvdata(vfd); + + spin_lock(&pxp->lock); + video_device_release(vfd); + spin_unlock(&pxp->lock); +} + +static int pxp_hw_init(struct pxps *pxp) +{ + struct fb_var_screeninfo var; + struct fb_fix_screeninfo fix; + int err; + + err = mxsfb_get_info(&var, &fix); + if (err) + return err; + + /* Pull PxP out of reset */ + __raw_writel(0, PXP_BASE_ADDR + HW_PXP_CTRL); + + /* Config defaults */ + pxp->active = NULL; + + pxp->s0_fmt = &pxp_s0_formats[0]; + pxp->drect.left = pxp->srect.left = 0; + pxp->drect.top = pxp->srect.top = 0; + pxp->drect.width = pxp->srect.width = pxp->s0_width = var.xres; + pxp->drect.height = pxp->srect.height = pxp->s0_height = var.yres; + pxp->s0_bgcolor = 0; + + pxp->output = 0; + err = pxp_set_fbinfo(pxp); + if (err) + return err; + + pxp->scaling = 0; + pxp->hflip = 0; + pxp->vflip = 0; + pxp->rotate = 0; + pxp->yuv = 0; + + pxp->overlay_state = 0; + pxp->global_alpha_state = 0; + pxp->global_alpha = 0; + pxp->local_alpha_state = 0; + pxp->s1_chromakey_state = 0; + pxp->s1_chromakey = -1; + pxp->s0_chromakey = -1; + + /* Write default h/w config */ + pxp_set_ctrl(pxp); + pxp_set_s0param(pxp); + pxp_set_s0crop(pxp); + pxp_set_oln(pxp); + pxp_set_olparam(pxp); + pxp_set_s0colorkey(pxp); + pxp_set_s1colorkey(pxp); + pxp_set_csc(pxp); + + return 0; +} + +static int pxp_open(struct file *file) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret = 0; + + mutex_lock(&pxp->mutex); + pxp->users++; + + if (pxp->users > 1) { + pxp->users--; + ret = -EBUSY; + goto out; + } +out: + mutex_unlock(&pxp->mutex); + if (ret) + return ret; + + pxp->next_queue_ended = 0; + pxp->workqueue = create_singlethread_workqueue("pxp"); + + videobuf_queue_dma_contig_init(&pxp->s0_vbq, + &pxp_vbq_ops, + &pxp->pdev->dev, + &pxp->lock, + V4L2_BUF_TYPE_VIDEO_OUTPUT, + V4L2_FIELD_NONE, + sizeof(struct pxp_buffer), pxp); + + return 0; +} + +static int pxp_close(struct file *file) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + if (pxp->workqueue) { + flush_workqueue(pxp->workqueue); + destroy_workqueue(pxp->workqueue); + } + + videobuf_stop(&pxp->s0_vbq); + videobuf_mmap_free(&pxp->s0_vbq); + pxp->active = NULL; + + mutex_lock(&pxp->mutex); + pxp->users--; + mutex_unlock(&pxp->mutex); + + return 0; +} + +static int pxp_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret; + + ret = videobuf_mmap_mapper(&pxp->s0_vbq, vma); + + return ret; +} + +static const struct v4l2_file_operations pxp_fops = { + .owner = THIS_MODULE, + .open = pxp_open, + .release = pxp_close, + .ioctl = video_ioctl2, + .mmap = pxp_mmap, +}; + +static const struct v4l2_ioctl_ops pxp_ioctl_ops = { + .vidioc_querycap = pxp_querycap, + + .vidioc_reqbufs = pxp_reqbufs, + .vidioc_querybuf = pxp_querybuf, + .vidioc_qbuf = pxp_qbuf, + .vidioc_dqbuf = pxp_dqbuf, + + .vidioc_streamon = pxp_streamon, + .vidioc_streamoff = pxp_streamoff, + + .vidioc_enum_output = pxp_enumoutput, + .vidioc_g_output = pxp_g_output, + .vidioc_s_output = pxp_s_output, + + .vidioc_enum_fmt_vid_out = pxp_enum_fmt_video_output, + .vidioc_try_fmt_vid_out = pxp_try_fmt_video_output, + .vidioc_g_fmt_vid_out = pxp_g_fmt_video_output, + .vidioc_s_fmt_vid_out = pxp_s_fmt_video_output, + + .vidioc_try_fmt_vid_out_overlay = pxp_try_fmt_output_overlay, + .vidioc_g_fmt_vid_out_overlay = pxp_g_fmt_output_overlay, + .vidioc_s_fmt_vid_out_overlay = pxp_s_fmt_output_overlay, + + .vidioc_g_fbuf = pxp_g_fbuf, + .vidioc_s_fbuf = pxp_s_fbuf, + + .vidioc_g_crop = pxp_g_crop, + .vidioc_s_crop = pxp_s_crop, + + .vidioc_queryctrl = pxp_queryctrl, + .vidioc_g_ctrl = pxp_g_ctrl, + .vidioc_s_ctrl = pxp_s_ctrl, +}; + +static const struct video_device pxp_template = { + .name = "PxP", + .vfl_type = V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_STREAMING, + .fops = &pxp_fops, + .release = pxp_release, + .minor = -1, + .ioctl_ops = &pxp_ioctl_ops, +}; + +static irqreturn_t pxp_irq(int irq, void *dev_id) +{ + struct pxps *pxp = (struct pxps *)dev_id; + struct videobuf_buffer *vb; + unsigned long flags; + + spin_lock_irqsave(&pxp->lock, flags); + + __raw_writel(BM_PXP_STAT_IRQ, PXP_BASE_ADDR + HW_PXP_STAT_CLR); + + if (list_empty(&pxp->outq)) { + pr_warning("irq: outq empty!!!\n"); + goto out; + } + + vb = list_entry(pxp->outq.next, struct videobuf_buffer, queue); + list_del_init(&vb->queue); + + vb->state = VIDEOBUF_DONE; + do_gettimeofday(&vb->ts); + vb->field_count++; + + wake_up(&vb->done); + wake_up(&pxp->done); +out: + spin_unlock_irqrestore(&pxp->lock, flags); + + return IRQ_HANDLED; +} + +static int pxp_probe(struct platform_device *pdev) +{ + struct pxps *pxp; + struct resource *res; + int irq; + int err = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!res || irq < 0) { + err = -ENODEV; + goto exit; + } + + pxp = kzalloc(sizeof(*pxp), GFP_KERNEL); + if (!pxp) { + dev_err(&pdev->dev, "failed to allocate control object\n"); + err = -ENOMEM; + goto exit; + } + + dev_set_drvdata(&pdev->dev, pxp); + pxp->res = res; + pxp->irq = irq; + + pxp->regs_virt = dma_alloc_coherent(NULL, + PAGE_ALIGN(sizeof + (struct pxp_registers)), + &pxp->regs_phys, GFP_KERNEL); + if (pxp->regs_virt == NULL) { + dev_err(&pdev->dev, "failed to allocate pxp_register object\n"); + err = -ENOMEM; + goto exit; + } + + init_waitqueue_head(&pxp->done); + + INIT_WORK(&pxp->work, pxp_next_handle); + INIT_LIST_HEAD(&pxp->outq); + INIT_LIST_HEAD(&pxp->nextq); + spin_lock_init(&pxp->lock); + mutex_init(&pxp->mutex); + + if (!request_mem_region(res->start, res->end - res->start + 1, + PXP_DRIVER_NAME)) { + err = -EBUSY; + goto freepxp; + } + + pxp->regs = (void __iomem *)res->start; /* it is already ioremapped */ + pxp->pdev = pdev; + + err = request_irq(pxp->irq, pxp_irq, 0, PXP_DRIVER_NAME, pxp); + + if (err) { + dev_err(&pdev->dev, "interrupt register failed\n"); + goto release; + } + + pxp->vdev = video_device_alloc(); + if (!pxp->vdev) { + dev_err(&pdev->dev, "video_device_alloc() failed\n"); + err = -ENOMEM; + goto freeirq; + } + + memcpy(pxp->vdev, &pxp_template, sizeof(pxp_template)); + video_set_drvdata(pxp->vdev, pxp); + + err = video_register_device(pxp->vdev, VFL_TYPE_GRABBER, 0); + if (err) { + dev_err(&pdev->dev, "failed to register video device\n"); + goto freevdev; + } + + err = pxp_hw_init(pxp); + if (err) { + dev_err(&pdev->dev, "failed to initialize hardware\n"); + goto freevdev; + } + + dev_info(&pdev->dev, "initialized\n"); + +exit: + return err; + +freevdev: + video_device_release(pxp->vdev); + +freeirq: + free_irq(pxp->irq, pxp); + +release: + release_mem_region(res->start, res->end - res->start + 1); + +freepxp: + kfree(pxp); + + return err; +} + +static int __devexit pxp_remove(struct platform_device *pdev) +{ + struct pxps *pxp = platform_get_drvdata(pdev); + + video_unregister_device(pxp->vdev); + video_device_release(pxp->vdev); + + if (pxp->regs_virt) + dma_free_coherent(0, PAGE_ALIGN(sizeof(struct pxp_registers)), + pxp->regs_virt, pxp->regs_phys); + kfree(pxp->outb); + kfree(pxp); + + return 0; +} + +#ifdef CONFIG_PM +static int pxp_suspend(struct platform_device *pdev, pm_message_t state) +{ + int i; + + while (__raw_readl(PXP_BASE_ADDR + HW_PXP_CTRL) & BM_PXP_CTRL_ENABLE) + ; + + for (i = 0; i < REGS1_NUMS; i++) + regs1[i] = + __raw_readl(PXP_BASE_ADDR + HW_PXP_CTRL + REG_OFFSET * i); + + for (i = 0; i < REGS2_NUMS; i++) + regs2[i] = + __raw_readl(PXP_BASE_ADDR + HW_PXP_S0COLORKEYLOW + + REG_OFFSET * i); + + for (i = 0; i < REGS3_NUMS; i++) + regs3[i] = + __raw_readl(PXP_BASE_ADDR + HW_PXP_OLn(0) + REG_OFFSET * i); + + __raw_writel(BM_PXP_CTRL_SFTRST, PXP_BASE_ADDR + HW_PXP_CTRL); + + return 0; +} + +static int pxp_resume(struct platform_device *pdev) +{ + int i; + + /* Pull PxP out of reset */ + __raw_writel(0, PXP_BASE_ADDR + HW_PXP_CTRL); + + for (i = 0; i < REGS1_NUMS; i++) + __raw_writel(regs1[i], + PXP_BASE_ADDR + HW_PXP_CTRL + REG_OFFSET * i); + + for (i = 0; i < REGS2_NUMS; i++) + __raw_writel(regs2[i], + PXP_BASE_ADDR + HW_PXP_S0COLORKEYLOW + + REG_OFFSET * i); + + for (i = 0; i < REGS3_NUMS; i++) + __raw_writel(regs3[i], + PXP_BASE_ADDR + HW_PXP_OLn(0) + REG_OFFSET * i); + + return 0; +} +#else +#define pxp_suspend NULL +#define pxp_resume NULL +#endif + +static struct platform_driver pxp_driver = { + .driver = { + .name = PXP_DRIVER_NAME, + }, + .probe = pxp_probe, + .remove = __exit_p(pxp_remove), + .suspend = pxp_suspend, + .resume = pxp_resume, +}; + +static int __devinit pxp_init(void) +{ + return platform_driver_register(&pxp_driver); +} + +static void __exit pxp_exit(void) +{ + platform_driver_unregister(&pxp_driver); +} + +module_init(pxp_init); +module_exit(pxp_exit); + +MODULE_DESCRIPTION("STMP37xx PxP driver"); +MODULE_AUTHOR("Matt Porter <mporter@embeddedalley.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxs_pxp.h b/drivers/media/video/mxs_pxp.h new file mode 100644 index 000000000000..6538388fa4cd --- /dev/null +++ b/drivers/media/video/mxs_pxp.h @@ -0,0 +1,158 @@ +/* + * Freescale MXS PxP driver + * + * Author: Matt Porter <mporter@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef CONFIG_ARCH_MX23 +#define BF_PXP_CTRL_OUTBUF_FORMAT(v) BF_PXP_CTRL_OUTPUT_RGB_FORMAT(v) + +#define BV_PXP_CTRL_OUTBUF_FORMAT__ARGB8888 \ + BV_PXP_CTRL_OUTPUT_RGB_FORMAT__ARGB8888 +#define BV_PXP_CTRL_OUTBUF_FORMAT__RGB888 \ + BV_PXP_CTRL_OUTPUT_RGB_FORMAT__RGB888 +#define BV_PXP_CTRL_OUTBUF_FORMAT__RGB888P \ + BV_PXP_CTRL_OUTPUT_RGB_FORMAT__RGB888P +#define BV_PXP_CTRL_OUTBUF_FORMAT__ARGB1555 \ + BV_PXP_CTRL_OUTPUT_RGB_FORMAT__ARGB1555 +#define BV_PXP_CTRL_OUTBUF_FORMAT__RGB565 \ + BV_PXP_CTRL_OUTPUT_RGB_FORMAT__RGB565 +#define BV_PXP_CTRL_OUTBUF_FORMAT__RGB555 \ + BV_PXP_CTRL_OUTPUT_RGB_FORMAT__RGB555 + +#define BF_PXP_OUTSIZE_WIDTH(v) BF_PXP_RGBSIZE_WIDTH(v) +#define BF_PXP_OUTSIZE_HEIGHT(v) BF_PXP_RGBSIZE_HEIGHT(v) + +/* The maximum down scaling factor is 1/2 */ +#define PXP_DOWNSCALE_THRESHOLD 0x2000 +#else +/* The maximum down scaling factor is 1/4 */ +#define PXP_DOWNSCALE_THRESHOLD 0x4000 +#endif + +struct pxp_overlay_registers { + u32 ol; + u32 olsize; + u32 olparam; + u32 olparam2; +}; + +/* Registers feed for PXP_NEXT */ +struct pxp_registers { + u32 ctrl; + u32 outbuf; + u32 outbuf2; + u32 outsize; + u32 s0buf; + u32 s0ubuf; + u32 s0vbuf; + u32 s0param; + u32 s0background; + u32 s0crop; + u32 s0scale; + u32 s0offset; + u32 s0colorkeylow; + u32 s0colorkeyhigh; + u32 olcolorkeylow; + u32 olcolorkeyhigh; + + struct pxp_overlay_registers ol0; + struct pxp_overlay_registers ol1; + struct pxp_overlay_registers ol2; + struct pxp_overlay_registers ol3; + struct pxp_overlay_registers ol4; + struct pxp_overlay_registers ol5; + struct pxp_overlay_registers ol6; + struct pxp_overlay_registers ol7; +}; + +struct pxp_buffer { + /* Must be first! */ + struct videobuf_buffer vb; + struct list_head queue; +}; + +struct pxps { + struct platform_device *pdev; + struct resource *res; + int irq; + void __iomem *regs; + + struct work_struct work; + struct workqueue_struct *workqueue; + spinlock_t lock; + struct mutex mutex; + int users; + + struct video_device *vdev; + + struct videobuf_queue s0_vbq; + struct videobuf_buffer *active; + struct list_head outq; + struct list_head nextq; + + int output; + u32 *outb; + dma_addr_t outb_phys; + + /* Current S0 configuration */ + struct pxp_data_format *s0_fmt; + u32 s0_width; + u32 s0_height; + u32 s0_bgcolor; + u32 s0_chromakey; + + struct v4l2_framebuffer fb; + struct v4l2_rect drect; + struct v4l2_rect srect; + + /* Transformation support */ + int scaling; + int hflip; + int vflip; + int rotate; + int yuv; + + /* Output overlay support */ + int overlay_state; + int global_alpha_state; + u8 global_alpha; + int local_alpha_state; + int s1_chromakey_state; + u32 s1_chromakey; + + /* PXP_NEXT */ + u32 regs_phys; + struct pxp_registers *regs_virt; + wait_queue_head_t done; + int next_queue_ended; +}; + +struct pxp_data_format { + char *name; + unsigned int bpp; + u32 fourcc; + enum v4l2_colorspace colorspace; + u32 ctrl_s0_fmt; +}; + +extern int mxsfb_get_info(struct fb_var_screeninfo *var, + struct fb_fix_screeninfo *fix); +extern void mxsfb_cfg_pxp(int enable, dma_addr_t pxp_phys); diff --git a/drivers/media/video/pxp.c b/drivers/media/video/pxp.c new file mode 100644 index 000000000000..0f0e89cd6806 --- /dev/null +++ b/drivers/media/video/pxp.c @@ -0,0 +1,1408 @@ +/* + * Freescale STMP378X PxP driver + * + * Author: Matt Porter <mporter@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/dma-mapping.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/vmalloc.h> +#include <linux/videodev2.h> + +#include <media/videobuf-dma-contig.h> +#include <media/v4l2-common.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> + +#include <mach/platform.h> +#include <mach/regs-pxp.h> +#include <mach/lcdif.h> + +#include "pxp.h" + +#define PXP_DRIVER_NAME "stmp3xxx-pxp" +#define PXP_DRIVER_MAJOR 1 +#define PXP_DRIVER_MINOR 0 + +#define PXP_DEF_BUFS 2 +#define PXP_MIN_PIX 8 + +#define V4L2_OUTPUT_TYPE_INTERNAL 4 + +#define PXP_WAITCON ((__raw_readl(HW_PXP_NEXT_ADDR) & BM_PXP_NEXT_ENABLED) \ + != BM_PXP_NEXT_ENABLED) + +#define REG_OFFSET 0x10 +#define REGS1_NUMS 16 +#define REGS2_NUMS 5 +#define REGS3_NUMS 32 +static u32 regs1[REGS1_NUMS]; +static u32 regs2[REGS2_NUMS]; +static u32 regs3[REGS3_NUMS]; + +static struct pxp_data_format pxp_s0_formats[] = { + { + .name = "24-bit RGB", + .bpp = 4, + .fourcc = V4L2_PIX_FMT_RGB24, + .colorspace = V4L2_COLORSPACE_SRGB, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__RGB888, + }, { + .name = "16-bit RGB 5:6:5", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_RGB565, + .colorspace = V4L2_COLORSPACE_SRGB, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__RGB565, + }, { + .name = "16-bit RGB 5:5:5", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_RGB555, + .colorspace = V4L2_COLORSPACE_SRGB, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__RGB555, + }, { + .name = "YUV 4:2:0 Planar", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_YUV420, + .colorspace = V4L2_COLORSPACE_JPEG, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__YUV420, + }, { + .name = "YUV 4:2:2 Planar", + .bpp = 2, + .fourcc = V4L2_PIX_FMT_YUV422P, + .colorspace = V4L2_COLORSPACE_JPEG, + .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__YUV422, + }, +}; + +struct v4l2_queryctrl pxp_controls[] = { + { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Horizontal Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Vertical Flip", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_PRIVATE_BASE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Rotation", + .minimum = 0, + .maximum = 270, + .step = 90, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_PRIVATE_BASE + 1, + .name = "Background Color", + .minimum = 0, + .maximum = 0xFFFFFF, + .step = 1, + .default_value = 0, + .flags = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_PRIVATE_BASE + 2, + .name = "Set S0 Chromakey", + .minimum = -1, + .maximum = 0xFFFFFF, + .step = 1, + .default_value = -1, + .flags = 0, + .type = V4L2_CTRL_TYPE_INTEGER, + }, { + .id = V4L2_CID_PRIVATE_BASE + 3, + .name = "YUV Colorspace", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + .flags = 0, + .type = V4L2_CTRL_TYPE_BOOLEAN, + }, +}; + +static void pxp_set_ctrl(struct pxps *pxp) +{ + u32 ctrl; + + ctrl = BF(pxp->s0_fmt->ctrl_s0_fmt, PXP_CTRL_S0_FORMAT); + ctrl |= + BF(BV_PXP_CTRL_OUTPUT_RGB_FORMAT__RGB888, PXP_CTRL_OUTPUT_RGB_FORMAT); + ctrl |= BM_PXP_CTRL_CROP; + + if (pxp->scaling) + ctrl |= BM_PXP_CTRL_SCALE; + if (pxp->vflip) + ctrl |= BM_PXP_CTRL_VFLIP; + if (pxp->hflip) + ctrl |= BM_PXP_CTRL_HFLIP; + if (pxp->rotate) + ctrl |= BF(pxp->rotate/90, PXP_CTRL_ROTATE); + + ctrl |= BM_PXP_CTRL_IRQ_ENABLE; + if (pxp->active) + ctrl |= BM_PXP_CTRL_ENABLE; + + __raw_writel(ctrl, HW_PXP_CTRL_ADDR); + pxp->regs_virt->ctrl = ctrl; +} + +static void pxp_set_rgbbuf(struct pxps *pxp) +{ + pxp->regs_virt->rgbbuf = pxp->outb_phys; + /* Always equal to the FB size */ + pxp->regs_virt->rgbsize = BF(pxp->fb.fmt.width, PXP_RGBSIZE_WIDTH) | + BF(pxp->fb.fmt.height, PXP_RGBSIZE_HEIGHT); +} + +static void pxp_set_s0colorkey(struct pxps *pxp) +{ + /* Low and high are set equal. V4L does not allow a chromakey range */ + if (pxp->s0_chromakey == -1) { + /* disable color key */ + pxp->regs_virt->s0colorkeylow = 0xFFFFFF; + pxp->regs_virt->s0colorkeyhigh = 0; + } else { + pxp->regs_virt->s0colorkeylow = pxp->s0_chromakey; + pxp->regs_virt->s0colorkeyhigh = pxp->s0_chromakey; + } +} + +static void pxp_set_s1colorkey(struct pxps *pxp) +{ + /* Low and high are set equal. V4L does not allow a chromakey range */ + if (pxp->s1_chromakey_state != 0 && pxp->s1_chromakey != -1) { + pxp->regs_virt->olcolorkeylow = pxp->s1_chromakey; + pxp->regs_virt->olcolorkeyhigh = pxp->s1_chromakey; + } else { + /* disable color key */ + pxp->regs_virt->olcolorkeylow = 0xFFFFFF; + pxp->regs_virt->olcolorkeyhigh = 0; + } +} + +static void pxp_set_oln(struct pxps *pxp) +{ + pxp->regs_virt->ol0.ol = (u32)pxp->fb.base; + pxp->regs_virt->ol0.olsize = + BF(pxp->fb.fmt.width >> 3, PXP_OLnSIZE_WIDTH) | + BF(pxp->fb.fmt.height >> 3, PXP_OLnSIZE_HEIGHT); +} + +static void pxp_set_olparam(struct pxps *pxp) +{ + u32 olparam; + struct v4l2_pix_format *fmt = &pxp->fb.fmt; + + olparam = BF(pxp->global_alpha, PXP_OLnPARAM_ALPHA); + if (fmt->pixelformat == V4L2_PIX_FMT_RGB24) + olparam |= + BF(BV_PXP_OLnPARAM_FORMAT__RGB888, PXP_OLnPARAM_FORMAT); + else + olparam |= + BF(BV_PXP_OLnPARAM_FORMAT__RGB565, PXP_OLnPARAM_FORMAT); + if (pxp->global_alpha_state) + olparam |= BF(BV_PXP_OLnPARAM_ALPHA_CNTL__Override, + PXP_OLnPARAM_ALPHA_CNTL); + if (pxp->s1_chromakey_state) + olparam |= BM_PXP_OLnPARAM_ENABLE_COLORKEY; + if (pxp->overlay_state) + olparam |= BM_PXP_OLnPARAM_ENABLE; + + pxp->regs_virt->ol0.olparam = olparam; +} + +static void pxp_set_s0param(struct pxps *pxp) +{ + u32 s0param; + + s0param = BF(pxp->drect.left >> 3, PXP_S0PARAM_XBASE); + s0param |= BF(pxp->drect.top >> 3, PXP_S0PARAM_YBASE); + s0param |= BF(pxp->s0_width >> 3, PXP_S0PARAM_WIDTH); + s0param |= BF(pxp->s0_height >> 3, PXP_S0PARAM_HEIGHT); + pxp->regs_virt->s0param = s0param; +} + +static void pxp_set_s0crop(struct pxps *pxp) +{ + u32 s0crop; + + s0crop = BF(pxp->srect.left >> 3, PXP_S0CROP_XBASE); + s0crop |= BF(pxp->srect.top >> 3, PXP_S0CROP_YBASE); + s0crop |= BF(pxp->drect.width >> 3, PXP_S0CROP_WIDTH); + s0crop |= BF(pxp->drect.height >> 3, PXP_S0CROP_HEIGHT); + pxp->regs_virt->s0crop = s0crop; +} + +static int pxp_set_scaling(struct pxps *pxp) +{ + int ret = 0; + u32 xscale, yscale, s0scale; + + if ((pxp->s0_fmt->fourcc != V4L2_PIX_FMT_YUV420) && + (pxp->s0_fmt->fourcc != V4L2_PIX_FMT_YUV422P)) { + pxp->scaling = 0; + ret = -EINVAL; + goto out; + } + + if ((pxp->srect.width == pxp->drect.width) && + (pxp->srect.height == pxp->drect.height)) { + pxp->scaling = 0; + goto out; + } + + pxp->scaling = 1; + xscale = pxp->srect.width * 0x1000 / pxp->drect.width; + yscale = pxp->srect.height * 0x1000 / pxp->drect.height; + s0scale = BF(yscale, PXP_S0SCALE_YSCALE) | + BF(xscale, PXP_S0SCALE_XSCALE); + pxp->regs_virt->s0scale = s0scale; + +out: + pxp_set_ctrl(pxp); + + return ret; +} + +static int pxp_set_fbinfo(struct pxps *pxp) +{ + struct fb_var_screeninfo var; + struct fb_fix_screeninfo fix; + struct v4l2_framebuffer *fb = &pxp->fb; + int err; + + err = stmp3xxxfb_get_info(&var, &fix); + + fb->fmt.width = var.xres; + fb->fmt.height = var.yres; + if (var.bits_per_pixel == 16) + fb->fmt.pixelformat = V4L2_PIX_FMT_RGB565; + else + fb->fmt.pixelformat = V4L2_PIX_FMT_RGB24; + fb->base = (void *)fix.smem_start; + return err; +} + +static void pxp_set_s0bg(struct pxps *pxp) +{ + pxp->regs_virt->s0background = pxp->s0_bgcolor; +} + +static void pxp_set_csc(struct pxps *pxp) +{ + if (pxp->yuv) { + /* YUV colorspace */ + __raw_writel(0x04030000, HW_PXP_CSCCOEFF0_ADDR); + __raw_writel(0x01230208, HW_PXP_CSCCOEFF1_ADDR); + __raw_writel(0x076b079c, HW_PXP_CSCCOEFF2_ADDR); + } else { + /* YCrCb colorspace */ + __raw_writel(0x84ab01f0, HW_PXP_CSCCOEFF0_ADDR); + __raw_writel(0x01230204, HW_PXP_CSCCOEFF1_ADDR); + __raw_writel(0x0730079c, HW_PXP_CSCCOEFF2_ADDR); + } +} + +static int pxp_set_cstate(struct pxps *pxp, struct v4l2_control *vc) +{ + + if (vc->id == V4L2_CID_HFLIP) + pxp->hflip = vc->value; + else if (vc->id == V4L2_CID_VFLIP) + pxp->vflip = vc->value; + else if (vc->id == V4L2_CID_PRIVATE_BASE) { + if (vc->value % 90) + return -ERANGE; + pxp->rotate = vc->value; + } else if (vc->id == V4L2_CID_PRIVATE_BASE + 1) { + pxp->s0_bgcolor = vc->value; + pxp_set_s0bg(pxp); + } else if (vc->id == V4L2_CID_PRIVATE_BASE + 2) { + pxp->s0_chromakey = vc->value; + pxp_set_s0colorkey(pxp); + } else if (vc->id == V4L2_CID_PRIVATE_BASE + 3) { + pxp->yuv = vc->value; + pxp_set_csc(pxp); + } + + pxp_set_ctrl(pxp); + + return 0; +} + +static int pxp_get_cstate(struct pxps *pxp, struct v4l2_control *vc) +{ + if (vc->id == V4L2_CID_HFLIP) + vc->value = pxp->hflip; + else if (vc->id == V4L2_CID_VFLIP) + vc->value = pxp->vflip; + else if (vc->id == V4L2_CID_PRIVATE_BASE) + vc->value = pxp->rotate; + else if (vc->id == V4L2_CID_PRIVATE_BASE + 1) + vc->value = pxp->s0_bgcolor; + else if (vc->id == V4L2_CID_PRIVATE_BASE + 2) + vc->value = pxp->s0_chromakey; + else if (vc->id == V4L2_CID_PRIVATE_BASE + 3) + vc->value = pxp->yuv; + + return 0; +} + +static int pxp_enumoutput(struct file *file, void *fh, + struct v4l2_output *o) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + if ((o->index < 0) || (o->index > 1)) + return -EINVAL; + + memset(o, 0, sizeof(struct v4l2_output)); + if (o->index == 0) { + strcpy(o->name, "PxP Display Output"); + pxp->output = 0; + } else { + strcpy(o->name, "PxP Virtual Output"); + pxp->output = 1; + } + o->type = V4L2_OUTPUT_TYPE_INTERNAL; + o->std = 0; + o->reserved[0] = pxp->outb_phys; + + return 0; +} + +static int pxp_g_output(struct file *file, void *fh, + unsigned int *i) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + *i = pxp->output; + + return 0; +} + +static int pxp_s_output(struct file *file, void *fh, + unsigned int i) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_pix_format *fmt = &pxp->fb.fmt; + int bpp; + + if ((i < 0) || (i > 1)) + return -EINVAL; + + if (pxp->outb) + goto out; + + /* Output buffer is same format as fbdev */ + if (fmt->pixelformat == V4L2_PIX_FMT_RGB24) + bpp = 4; + else + bpp = 2; + + pxp->outb = kmalloc(fmt->width * fmt->height * bpp, GFP_KERNEL); + pxp->outb_phys = virt_to_phys(pxp->outb); + dma_map_single(NULL, pxp->outb, + fmt->width * fmt->height * bpp, DMA_TO_DEVICE); + +out: + pxp_set_rgbbuf(pxp); + + return 0; +} + +static int pxp_enum_fmt_video_output(struct file *file, void *fh, + struct v4l2_fmtdesc *fmt) +{ + enum v4l2_buf_type type = fmt->type; + int index = fmt->index; + + if ((fmt->index < 0) || (fmt->index >= ARRAY_SIZE(pxp_s0_formats))) + return -EINVAL; + + memset(fmt, 0, sizeof(struct v4l2_fmtdesc)); + fmt->index = index; + fmt->type = type; + fmt->pixelformat = pxp_s0_formats[index].fourcc; + strcpy(fmt->description, pxp_s0_formats[index].name); + + return 0; +} + +static int pxp_g_fmt_video_output(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct v4l2_pix_format *pf = &f->fmt.pix; + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct pxp_data_format *fmt = pxp->s0_fmt; + + pf->width = pxp->s0_width; + pf->height = pxp->s0_height; + pf->pixelformat = fmt->fourcc; + pf->field = V4L2_FIELD_NONE; + pf->bytesperline = fmt->bpp * pf->width; + pf->sizeimage = pf->bytesperline * pf->height; + pf->colorspace = fmt->colorspace; + pf->priv = 0; + + return 0; +} + +static struct pxp_data_format *pxp_get_format(struct v4l2_format *f) +{ + struct pxp_data_format *fmt; + int i; + + for (i = 0; i < ARRAY_SIZE(pxp_s0_formats); i++) { + fmt = &pxp_s0_formats[i]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (i == ARRAY_SIZE(pxp_s0_formats)) + return NULL; + + return &pxp_s0_formats[i]; +} + +static int pxp_try_fmt_video_output(struct file *file, void *fh, + struct v4l2_format *f) +{ + int w = f->fmt.pix.width; + int h = f->fmt.pix.height; + struct pxp_data_format *fmt = pxp_get_format(f); + + if (!fmt) + return -EINVAL; + + w = min(w, 2040); + w = max(w, 8); + h = min(h, 2040); + h = max(h, 8); + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = w; + f->fmt.pix.height = h; + f->fmt.pix.pixelformat = fmt->fourcc; + + return 0; +} + +static int pxp_s_fmt_video_output(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_pix_format *pf = &f->fmt.pix; + int ret = pxp_try_fmt_video_output(file, fh, f); + + if (ret == 0) { + pxp->s0_fmt = pxp_get_format(f); + pxp->s0_width = pf->width; + pxp->s0_height = pf->height; + pxp_set_ctrl(pxp); + pxp_set_s0param(pxp); + } + + return ret; +} + +static int pxp_g_fmt_output_overlay(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_window *wf = &f->fmt.win; + + memset(wf, 0, sizeof(struct v4l2_window)); + wf->chromakey = pxp->s1_chromakey; + wf->global_alpha = pxp->global_alpha; + wf->field = V4L2_FIELD_NONE; + wf->clips = NULL; + wf->clipcount = 0; + wf->bitmap = NULL; + wf->w.left = pxp->srect.left; + wf->w.top = pxp->srect.top; + wf->w.width = pxp->srect.width; + wf->w.height = pxp->srect.height; + + return 0; +} + +static int pxp_try_fmt_output_overlay(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_window *wf = &f->fmt.win; + struct v4l2_rect srect; + u32 s1_chromakey = wf->chromakey; + u8 global_alpha = wf->global_alpha; + + memcpy(&srect, &(wf->w), sizeof(struct v4l2_rect)); + + pxp_g_fmt_output_overlay(file, fh, f); + + wf->chromakey = s1_chromakey; + wf->global_alpha = global_alpha; + + /* Constrain parameters to the input buffer */ + wf->w.left = srect.left; + wf->w.top = srect.top; + wf->w.width = min(srect.width, ((__s32)pxp->s0_width - wf->w.left)); + wf->w.height = min(srect.height, ((__s32)pxp->s0_height - wf->w.top)); + + return 0; +} + +static int pxp_s_fmt_output_overlay(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + struct v4l2_window *wf = &f->fmt.win; + int ret = pxp_try_fmt_output_overlay(file, fh, f); + + if (ret == 0) { + pxp->srect.left = wf->w.left; + pxp->srect.top = wf->w.top; + pxp->srect.width = wf->w.width; + pxp->srect.height = wf->w.height; + pxp->global_alpha = wf->global_alpha; + pxp->s1_chromakey = wf->chromakey; + pxp_set_s0param(pxp); + pxp_set_s0crop(pxp); + pxp_set_scaling(pxp); + pxp_set_olparam(pxp); + pxp_set_s1colorkey(pxp); + } + + return ret; +} + +static int pxp_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *r) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_reqbufs(&pxp->s0_vbq, r); +} + +static int pxp_querybuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_querybuf(&pxp->s0_vbq, b); +} + +static int pxp_qbuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_qbuf(&pxp->s0_vbq, b); +} + +static int pxp_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + return videobuf_dqbuf(&pxp->s0_vbq, b, file->f_flags & O_NONBLOCK); +} + +static int pxp_streamon(struct file *file, void *priv, + enum v4l2_buf_type t) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret = 0; + + if ((t != V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -EINVAL; + + ret = videobuf_streamon(&pxp->s0_vbq); + + if (!ret && (pxp->output == 0)) + stmp3xxxfb_cfg_pxp(1, pxp->outb_phys); + + return ret; +} + +static int pxp_streamoff(struct file *file, void *priv, + enum v4l2_buf_type t) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret = 0; + + if ((t != V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -EINVAL; + + ret = videobuf_streamoff(&pxp->s0_vbq); + + if (!ret) + stmp3xxxfb_cfg_pxp(0, 0); + + return ret; +} + +static int pxp_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned *size) +{ + struct pxps *pxp = q->priv_data; + + *size = pxp->s0_width * pxp->s0_height * pxp->s0_fmt->bpp; + + if (0 == *count) + *count = PXP_DEF_BUFS; + + return 0; +} + +static void pxp_buf_free(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + if (in_interrupt()) + BUG(); + + videobuf_dma_contig_free(q, vb); + + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static int pxp_buf_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct pxps *pxp = q->priv_data; + int ret = 0; + + vb->width = pxp->s0_width; + vb->height = pxp->s0_height; + vb->size = vb->width * vb->height * pxp->s0_fmt->bpp; + vb->field = V4L2_FIELD_NONE; + vb->state = VIDEOBUF_NEEDS_INIT; + + ret = videobuf_iolock(q, vb, NULL); + if (ret) + goto fail; + vb->state = VIDEOBUF_PREPARED; + + return 0; + +fail: + pxp_buf_free(q, vb); + return ret; +} + +static void pxp_buf_next(struct pxps *pxp) +{ + dma_addr_t Y, U, V; + + if (pxp->active) { + pxp->active->state = VIDEOBUF_ACTIVE; + Y = videobuf_to_dma_contig(pxp->active); + pxp->regs_virt->s0buf = Y; + if ((pxp->s0_fmt->fourcc == V4L2_PIX_FMT_YUV420) || + (pxp->s0_fmt->fourcc == V4L2_PIX_FMT_YUV422P)) { + int s = 1; /* default to YUV 4:2:2 */ + if (pxp->s0_fmt->fourcc == V4L2_PIX_FMT_YUV420) + s = 2; + U = Y + (pxp->s0_width * pxp->s0_height); + V = U + ((pxp->s0_width * pxp->s0_height) >> s); + pxp->regs_virt->s0ubuf = U; + pxp->regs_virt->s0vbuf = V; + } + pxp->regs_virt->ctrl = + __raw_readl(HW_PXP_CTRL_ADDR) | BM_PXP_CTRL_ENABLE; + } + + __raw_writel(pxp->regs_phys, HW_PXP_NEXT_ADDR); +} + +static void pxp_next_handle(struct work_struct *w) +{ + struct pxps *pxp = container_of(w, struct pxps, work); + struct pxp_buffer *buf, *next; + unsigned long flags; + + if (pxp->next_queue_ended == 1) + return; + + spin_lock_irqsave(&pxp->lock, flags); + + while (!list_empty(&pxp->nextq)) { + spin_unlock_irqrestore(&pxp->lock, flags); + + if (!wait_event_interruptible_timeout(pxp->done, PXP_WAITCON, + 5 * HZ) || signal_pending(current)) { + spin_lock_irqsave(&pxp->lock, flags); + list_for_each_entry_safe(buf, next, &pxp->nextq, queue) + list_del(&buf->queue); + spin_unlock_irqrestore(&pxp->lock, flags); + pxp->next_queue_ended = 1; + return; + } + + spin_lock_irqsave(&pxp->lock, flags); + buf = list_entry(pxp->nextq.next, + struct pxp_buffer, + queue); + list_del_init(&buf->queue); + pxp->active = &buf->vb; + pxp->active->state = VIDEOBUF_QUEUED; + pxp_buf_next(pxp); + } + + spin_unlock_irqrestore(&pxp->lock, flags); +} + +static void pxp_buf_queue(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct pxps *pxp = q->priv_data; + struct pxp_buffer *buf; + unsigned long flags; + + spin_lock_irqsave(&pxp->lock, flags); + + if (list_empty(&pxp->outq)) { + list_add_tail(&vb->queue, &pxp->outq); + vb->state = VIDEOBUF_QUEUED; + + pxp->active = vb; + pxp_buf_next(pxp); + } else { + list_add_tail(&vb->queue, &pxp->outq); + + buf = container_of(vb, struct pxp_buffer, vb); + list_add_tail(&buf->queue, &pxp->nextq); + queue_work(pxp->workqueue, &pxp->work); + } + + spin_unlock_irqrestore(&pxp->lock, flags); +} + +static void pxp_buf_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + pxp_buf_free(q, vb); +} + +static struct videobuf_queue_ops pxp_vbq_ops = { + .buf_setup = pxp_buf_setup, + .buf_prepare = pxp_buf_prepare, + .buf_queue = pxp_buf_queue, + .buf_release = pxp_buf_release, +}; + +static int pxp_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + memset(cap, 0, sizeof(*cap)); + strcpy(cap->driver, "pxp"); + strcpy(cap->card, "pxp"); + strlcpy(cap->bus_info, dev_name(&pxp->pdev->dev), sizeof(cap->bus_info)); + + cap->version = (PXP_DRIVER_MAJOR << 8) + PXP_DRIVER_MINOR; + + cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_OUTPUT_OVERLAY | + V4L2_CAP_STREAMING; + + return 0; +} + +static int pxp_g_fbuf(struct file *file, void *priv, + struct v4l2_framebuffer *fb) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + memset(fb, 0, sizeof(*fb)); + + fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY | + V4L2_FBUF_CAP_CHROMAKEY | + V4L2_FBUF_CAP_LOCAL_ALPHA | + V4L2_FBUF_CAP_GLOBAL_ALPHA; + + if (pxp->global_alpha_state) + fb->flags |= V4L2_FBUF_FLAG_GLOBAL_ALPHA; + if (pxp->local_alpha_state) + fb->flags |= V4L2_FBUF_FLAG_LOCAL_ALPHA; + if (pxp->s1_chromakey_state) + fb->flags |= V4L2_FBUF_FLAG_CHROMAKEY; + + return 0; +} + +static int pxp_s_fbuf(struct file *file, void *priv, + struct v4l2_framebuffer *fb) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + pxp->overlay_state = + (fb->flags & V4L2_FBUF_FLAG_OVERLAY) != 0; + pxp->global_alpha_state = + (fb->flags & V4L2_FBUF_FLAG_GLOBAL_ALPHA) != 0; + pxp->local_alpha_state = + (fb->flags & V4L2_FBUF_FLAG_LOCAL_ALPHA) != 0; + /* Global alpha overrides local alpha if both are requested */ + if (pxp->global_alpha_state && pxp->local_alpha_state) + pxp->local_alpha_state = 0; + pxp->s1_chromakey_state = + (fb->flags & V4L2_FBUF_FLAG_CHROMAKEY) != 0; + + pxp_set_olparam(pxp); + pxp_set_s0crop(pxp); + pxp_set_scaling(pxp); + + return 0; +} + +static int pxp_g_crop(struct file *file, void *fh, + struct v4l2_crop *c) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY) + return -EINVAL; + + c->c.left = pxp->drect.left; + c->c.top = pxp->drect.top; + c->c.width = pxp->drect.width; + c->c.height = pxp->drect.height; + + return 0; +} + +static int pxp_s_crop(struct file *file, void *fh, + struct v4l2_crop *c) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int l = c->c.left; + int t = c->c.top; + int w = c->c.width; + int h = c->c.height; + int fbw = pxp->fb.fmt.width; + int fbh = pxp->fb.fmt.height; + + if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY) + return -EINVAL; + + /* Constrain parameters to FB limits */ + w = min(w, fbw); + w = max(w, PXP_MIN_PIX); + h = min(h, fbh); + h = max(h, PXP_MIN_PIX); + if ((l + w) > fbw) + l = 0; + if ((t + h) > fbh) + t = 0; + + /* Round up values to PxP pixel block */ + l = roundup(l, PXP_MIN_PIX); + t = roundup(t, PXP_MIN_PIX); + w = roundup(w, PXP_MIN_PIX); + h = roundup(h, PXP_MIN_PIX); + + pxp->drect.left = l; + pxp->drect.top = t; + pxp->drect.width = w; + pxp->drect.height = h; + + pxp_set_s0param(pxp); + pxp_set_s0crop(pxp); + pxp_set_scaling(pxp); + + return 0; +} + +static int pxp_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) + if (qc->id && qc->id == pxp_controls[i].id) { + memcpy(qc, &(pxp_controls[i]), sizeof(*qc)); + return 0; + } + + return -EINVAL; +} + +static int pxp_g_ctrl(struct file *file, void *priv, + struct v4l2_control *vc) +{ + int i; + + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) + if (vc->id == pxp_controls[i].id) + return pxp_get_cstate(pxp, vc); + + return -EINVAL; +} + +static int pxp_s_ctrl(struct file *file, void *priv, + struct v4l2_control *vc) +{ + int i; + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) + if (vc->id == pxp_controls[i].id) { + if (vc->value < pxp_controls[i].minimum || + vc->value > pxp_controls[i].maximum) + return -ERANGE; + return pxp_set_cstate(pxp, vc); + } + + return -EINVAL; +} + +void pxp_release(struct video_device *vfd) +{ + struct pxps *pxp = video_get_drvdata(vfd); + + spin_lock(&pxp->lock); + video_device_release(vfd); + spin_unlock(&pxp->lock); +} + +static int pxp_hw_init(struct pxps *pxp) +{ + struct fb_var_screeninfo var; + struct fb_fix_screeninfo fix; + int err; + + err = stmp3xxxfb_get_info(&var, &fix); + if (err) + return err; + + /* Pull PxP out of reset */ + __raw_writel(0, HW_PXP_CTRL_ADDR); + + /* Config defaults */ + pxp->active = NULL; + + pxp->s0_fmt = &pxp_s0_formats[0]; + pxp->drect.left = pxp->srect.left = 0; + pxp->drect.top = pxp->srect.top = 0; + pxp->drect.width = pxp->srect.width = pxp->s0_width = var.xres; + pxp->drect.height = pxp->srect.height = pxp->s0_height = var.yres; + pxp->s0_bgcolor = 0; + + pxp->output = 0; + err = pxp_set_fbinfo(pxp); + if (err) + return err; + + pxp->scaling = 0; + pxp->hflip = 0; + pxp->vflip = 0; + pxp->rotate = 0; + pxp->yuv = 0; + + pxp->overlay_state = 0; + pxp->global_alpha_state = 0; + pxp->global_alpha = 0; + pxp->local_alpha_state = 0; + pxp->s1_chromakey_state = 0; + pxp->s1_chromakey = -1; + pxp->s0_chromakey = -1; + + /* Write default h/w config */ + pxp_set_ctrl(pxp); + pxp_set_s0param(pxp); + pxp_set_s0crop(pxp); + pxp_set_oln(pxp); + pxp_set_olparam(pxp); + pxp_set_s0colorkey(pxp); + pxp_set_s1colorkey(pxp); + pxp_set_csc(pxp); + + return 0; +} + +static int pxp_open(struct file *file) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret = 0; + + mutex_lock(&pxp->mutex); + pxp->users++; + + if (pxp->users > 1) { + pxp->users--; + ret = -EBUSY; + goto out; + } +out: + mutex_unlock(&pxp->mutex); + if (ret) + return ret; + + pxp->next_queue_ended = 0; + pxp->workqueue = create_singlethread_workqueue("pxp"); + + videobuf_queue_dma_contig_init(&pxp->s0_vbq, + &pxp_vbq_ops, + &pxp->pdev->dev, + &pxp->lock, + V4L2_BUF_TYPE_VIDEO_OUTPUT, + V4L2_FIELD_NONE, + sizeof(struct pxp_buffer), + pxp); + + return 0; +} + +static int pxp_close(struct file *file) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + + if (pxp->workqueue) + destroy_workqueue(pxp->workqueue); + + videobuf_stop(&pxp->s0_vbq); + videobuf_mmap_free(&pxp->s0_vbq); + pxp->active = NULL; + + mutex_lock(&pxp->mutex); + pxp->users--; + mutex_unlock(&pxp->mutex); + + return 0; +} + +static int pxp_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct pxps *pxp = video_get_drvdata(video_devdata(file)); + int ret; + + ret = videobuf_mmap_mapper(&pxp->s0_vbq, vma); + + return ret; +} + +static const struct v4l2_file_operations pxp_fops = { + .owner = THIS_MODULE, + .open = pxp_open, + .release = pxp_close, + .ioctl = video_ioctl2, + .mmap = pxp_mmap, +}; + +static const struct v4l2_ioctl_ops pxp_ioctl_ops = { + .vidioc_querycap = pxp_querycap, + + .vidioc_reqbufs = pxp_reqbufs, + .vidioc_querybuf = pxp_querybuf, + .vidioc_qbuf = pxp_qbuf, + .vidioc_dqbuf = pxp_dqbuf, + + .vidioc_streamon = pxp_streamon, + .vidioc_streamoff = pxp_streamoff, + + .vidioc_enum_output = pxp_enumoutput, + .vidioc_g_output = pxp_g_output, + .vidioc_s_output = pxp_s_output, + + .vidioc_enum_fmt_vid_out = pxp_enum_fmt_video_output, + .vidioc_try_fmt_vid_out = pxp_try_fmt_video_output, + .vidioc_g_fmt_vid_out = pxp_g_fmt_video_output, + .vidioc_s_fmt_vid_out = pxp_s_fmt_video_output, + + .vidioc_try_fmt_vid_out_overlay = pxp_try_fmt_output_overlay, + .vidioc_g_fmt_vid_out_overlay = pxp_g_fmt_output_overlay, + .vidioc_s_fmt_vid_out_overlay = pxp_s_fmt_output_overlay, + + .vidioc_g_fbuf = pxp_g_fbuf, + .vidioc_s_fbuf = pxp_s_fbuf, + + .vidioc_g_crop = pxp_g_crop, + .vidioc_s_crop = pxp_s_crop, + + .vidioc_queryctrl = pxp_queryctrl, + .vidioc_g_ctrl = pxp_g_ctrl, + .vidioc_s_ctrl = pxp_s_ctrl, +}; + +static const struct video_device pxp_template = { + .name = "PxP", + .vfl_type = V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_OVERLAY | + V4L2_CAP_STREAMING, + .fops = &pxp_fops, + .release = pxp_release, + .minor = -1, + .ioctl_ops = &pxp_ioctl_ops, +}; + +static irqreturn_t pxp_irq(int irq, void *dev_id) +{ + struct pxps *pxp = (struct pxps *)dev_id; + struct videobuf_buffer *vb; + unsigned long flags; + + spin_lock_irqsave(&pxp->lock, flags); + + __raw_writel(BM_PXP_STAT_IRQ, HW_PXP_STAT_CLR_ADDR); + + if (list_empty(&pxp->outq)) { + pr_warning("irq: outq empty!!!\n"); + goto out; + } + + vb = list_entry(pxp->outq.next, + struct videobuf_buffer, + queue); + list_del_init(&vb->queue); + + vb->state = VIDEOBUF_DONE; + do_gettimeofday(&vb->ts); + vb->field_count++; + + wake_up(&vb->done); + wake_up(&pxp->done); +out: + spin_unlock_irqrestore(&pxp->lock, flags); + + return IRQ_HANDLED; +} + +static int pxp_notifier_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + struct pxps *pxp = container_of(self, struct pxps, nb); + + switch (event) { + case STMP3XXX_LCDIF_PANEL_INIT: + pxp_set_fbinfo(pxp); + break; + default: + break; + } + return 0; +} + +static int pxp_probe(struct platform_device *pdev) +{ + struct pxps *pxp; + struct resource *res; + int irq; + int err = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!res || irq < 0) { + err = -ENODEV; + goto exit; + } + + pxp = kzalloc(sizeof(*pxp), GFP_KERNEL); + if (!pxp) { + dev_err(&pdev->dev, "failed to allocate control object\n"); + err = -ENOMEM; + goto exit; + } + + dev_set_drvdata(&pdev->dev, pxp); + pxp->res = res; + pxp->irq = irq; + + pxp->regs_virt = dma_alloc_coherent(NULL, + PAGE_ALIGN(sizeof(struct pxp_registers)), + &pxp->regs_phys, GFP_KERNEL); + if (pxp->regs_virt == NULL) { + dev_err(&pdev->dev, "failed to allocate pxp_register object\n"); + err = -ENOMEM; + goto exit; + } + + init_waitqueue_head(&pxp->done); + + INIT_WORK(&pxp->work, pxp_next_handle); + INIT_LIST_HEAD(&pxp->outq); + INIT_LIST_HEAD(&pxp->nextq); + spin_lock_init(&pxp->lock); + mutex_init(&pxp->mutex); + + if (!request_mem_region(res->start, res->end - res->start + 1, + PXP_DRIVER_NAME)) { + err = -EBUSY; + goto freepxp; + } + + pxp->regs = (void __iomem *)res->start; /* it is already ioremapped */ + pxp->pdev = pdev; + + err = request_irq(pxp->irq, pxp_irq, 0, PXP_DRIVER_NAME, pxp); + + if (err) { + dev_err(&pdev->dev, "interrupt register failed\n"); + goto release; + } + + pxp->vdev = video_device_alloc(); + if (!pxp->vdev) { + dev_err(&pdev->dev, "video_device_alloc() failed\n"); + err = -ENOMEM; + goto freeirq; + } + + memcpy(pxp->vdev, &pxp_template, sizeof(pxp_template)); + video_set_drvdata(pxp->vdev, pxp); + + err = video_register_device(pxp->vdev, VFL_TYPE_GRABBER, 0); + if (err) { + dev_err(&pdev->dev, "failed to register video device\n"); + goto freevdev; + } + + err = pxp_hw_init(pxp); + if (err) { + dev_err(&pdev->dev, "failed to initialize hardware\n"); + goto freevdev; + } + + pxp->nb.notifier_call = pxp_notifier_callback, + stmp3xxx_lcdif_register_client(&pxp->nb); + dev_info(&pdev->dev, "initialized\n"); + +exit: + return err; + +freevdev: + video_device_release(pxp->vdev); + +freeirq: + free_irq(pxp->irq, pxp); + +release: + release_mem_region(res->start, res->end - res->start + 1); + +freepxp: + kfree(pxp); + + return err; +} + +static int __devexit pxp_remove(struct platform_device *pdev) +{ + struct pxps *pxp = platform_get_drvdata(pdev); + + stmp3xxx_lcdif_unregister_client(&pxp->nb); + video_unregister_device(pxp->vdev); + video_device_release(pxp->vdev); + + if (pxp->regs_virt) + dma_free_coherent(0, PAGE_ALIGN(sizeof(struct pxp_registers)), + pxp->regs_virt, pxp->regs_phys); + kfree(pxp->outb); + kfree(pxp); + + return 0; +} + +#ifdef CONFIG_PM +static int pxp_suspend(struct platform_device *pdev, pm_message_t state) +{ + int i; + + while (__raw_readl(HW_PXP_CTRL_ADDR) & BM_PXP_CTRL_ENABLE) + ; + + for (i = 0; i < REGS1_NUMS; i++) + regs1[i] = __raw_readl(HW_PXP_CTRL_ADDR + REG_OFFSET * i); + + for (i = 0; i < REGS2_NUMS; i++) + regs2[i] = __raw_readl(HW_PXP_PAGETABLE_ADDR + REG_OFFSET * i); + + for (i = 0; i < REGS3_NUMS; i++) + regs3[i] = __raw_readl(HW_PXP_OLn_ADDR(0) + REG_OFFSET * i); + + __raw_writel(BM_PXP_CTRL_SFTRST, HW_PXP_CTRL_ADDR); + + return 0; +} + +static int pxp_resume(struct platform_device *pdev) +{ + int i; + + /* Pull PxP out of reset */ + __raw_writel(0, HW_PXP_CTRL_ADDR); + + for (i = 0; i < REGS1_NUMS; i++) + __raw_writel(regs1[i], HW_PXP_CTRL_ADDR + REG_OFFSET * i); + + for (i = 0; i < REGS2_NUMS; i++) + __raw_writel(regs2[i], HW_PXP_PAGETABLE_ADDR + REG_OFFSET * i); + + for (i = 0; i < REGS3_NUMS; i++) + __raw_writel(regs3[i], HW_PXP_OLn_ADDR(0) + REG_OFFSET * i); + + return 0; +} +#else +#define pxp_suspend NULL +#define pxp_resume NULL +#endif + +static struct platform_driver pxp_driver = { + .driver = { + .name = PXP_DRIVER_NAME, + }, + .probe = pxp_probe, + .remove = __exit_p(pxp_remove), + .suspend = pxp_suspend, + .resume = pxp_resume, +}; + + +static int __devinit pxp_init(void) +{ + return platform_driver_register(&pxp_driver); +} + +static void __exit pxp_exit(void) +{ + platform_driver_unregister(&pxp_driver); +} + +module_init(pxp_init); +module_exit(pxp_exit); + +MODULE_DESCRIPTION("STMP37xx PxP driver"); +MODULE_AUTHOR("Matt Porter <mporter@embeddedalley.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/pxp.h b/drivers/media/video/pxp.h new file mode 100644 index 000000000000..11b41dcc6ade --- /dev/null +++ b/drivers/media/video/pxp.h @@ -0,0 +1,130 @@ +/* + * Freescale STMP378X PxP driver + * + * Author: Matt Porter <mporter@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +struct pxp_overlay_registers { + u32 ol; + u32 olsize; + u32 olparam; + u32 olparam2; +}; + +/* Registers feed for PXP_NEXT */ +struct pxp_registers { + u32 ctrl; + u32 rgbbuf; + u32 rgbbuf2; + u32 rgbsize; + u32 s0buf; + u32 s0ubuf; + u32 s0vbuf; + u32 s0param; + u32 s0background; + u32 s0crop; + u32 s0scale; + u32 s0offset; + u32 s0colorkeylow; + u32 s0colorkeyhigh; + u32 olcolorkeylow; + u32 olcolorkeyhigh; + + struct pxp_overlay_registers ol0; + struct pxp_overlay_registers ol1; + struct pxp_overlay_registers ol2; + struct pxp_overlay_registers ol3; + struct pxp_overlay_registers ol4; + struct pxp_overlay_registers ol5; + struct pxp_overlay_registers ol6; + struct pxp_overlay_registers ol7; +}; + +struct pxp_buffer { + /* Must be first! */ + struct videobuf_buffer vb; + struct list_head queue; +}; + +struct pxps { + struct platform_device *pdev; + struct resource *res; + int irq; + void __iomem *regs; + + struct work_struct work; + struct workqueue_struct *workqueue; + spinlock_t lock; + struct mutex mutex; + int users; + + struct video_device *vdev; + + struct videobuf_queue s0_vbq; + struct videobuf_buffer *active; + struct list_head outq; + struct list_head nextq; + + int output; + u32 *outb; + dma_addr_t outb_phys; + + /* Current S0 configuration */ + struct pxp_data_format *s0_fmt; + u32 s0_width; + u32 s0_height; + u32 s0_bgcolor; + u32 s0_chromakey; + + struct v4l2_framebuffer fb; + struct v4l2_rect drect; + struct v4l2_rect srect; + + /* Transformation support */ + int scaling; + int hflip; + int vflip; + int rotate; + int yuv; + + /* Output overlay support */ + int overlay_state; + int global_alpha_state; + u8 global_alpha; + int local_alpha_state; + int s1_chromakey_state; + u32 s1_chromakey; + + /* PXP_NEXT */ + u32 regs_phys; + struct pxp_registers *regs_virt; + wait_queue_head_t done; + int next_queue_ended; + + /* notifier for PXP when fb changed */ + struct notifier_block nb; +}; + +struct pxp_data_format { + char *name; + unsigned int bpp; + u32 fourcc; + enum v4l2_colorspace colorspace; + u32 ctrl_s0_fmt; +}; + +extern int stmp3xxxfb_get_info(struct fb_var_screeninfo *var, + struct fb_fix_screeninfo *fix); +extern void stmp3xxxfb_cfg_pxp(int enable, dma_addr_t pxp_phys); diff --git a/drivers/media/video/videobuf-dma-contig.c b/drivers/media/video/videobuf-dma-contig.c index d09ce83a9429..dc4f32bbd83d 100644 --- a/drivers/media/video/videobuf-dma-contig.c +++ b/drivers/media/video/videobuf-dma-contig.c @@ -319,7 +319,7 @@ static int __videobuf_mmap_mapper(struct videobuf_queue *q, mem->size = PAGE_ALIGN(q->bufs[first]->bsize); mem->vaddr = dma_alloc_coherent(q->dev, mem->size, - &mem->dma_handle, GFP_KERNEL); + &mem->dma_handle, GFP_DMA); if (!mem->vaddr) { dev_err(q->dev, "dma_alloc_coherent size %ld failed\n", mem->size); @@ -333,7 +333,7 @@ static int __videobuf_mmap_mapper(struct videobuf_queue *q, size = vma->vm_end - vma->vm_start; size = (size < mem->size) ? size : mem->size; - vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + vma->vm_page_prot = pgprot_writethru(vma->vm_page_prot); retval = remap_pfn_range(vma, vma->vm_start, mem->dma_handle >> PAGE_SHIFT, size, vma->vm_page_prot); |